dev
Marko Semet 2020-03-13 17:16:49 +01:00
parent bc020ce516
commit f9504a9605
10 changed files with 1153 additions and 0 deletions

1
.gitignore vendored
View File

@ -129,3 +129,4 @@ dmypy.json
# Pyre type checker
.pyre/
*.glade~

10
setup.py 100644
View File

@ -0,0 +1,10 @@
from distutils.core import setup
setup(name="winestarter",
version="0.0.1-dev",
description="Setup tools for wine",
author="Marko Semet",
author_email="marko@marko10-000.de",
url="https://marko10-000.de/project/winestarter",
packages=["winestarter"]
)

View File

@ -0,0 +1,47 @@
from . import gui
import asyncio
import argparse
import threading
LOOP = asyncio.get_event_loop()
# Args parse
parser = argparse.ArgumentParser(description="Wine Starter")
parser.add_argument("--config", "-c", dest="configs", type=str,
action="append", nargs=1, help="Configuration to make software runable.")
parser.add_argument("--not-run", nargs="?", const=True,
default=False, help="Doesn't start software directly.")
parser.add_argument("start", type=str, nargs="?",
const=None, help="Instance to run.")
args = parser.parse_args()
print(repr(args))
# Run gui and asyncio
_terminate = threading.Lock()
_terminate.acquire()
async def _run_async():
# Wait for gui thread init
while not _terminate.acquire(blocking=False):
await asyncio.sleep(0)
# Set main window
# Wait for async ends
while not _terminate.acquire(blocking=False):
await asyncio.sleep(0.1)
def _run_gui():
def inited_func():
_terminate.release()
gui.run_gui(inited=inited_func, configs=list(
map(lambda x: x[0], args.configs)))
_terminate.release()
threading.Thread(target=_run_gui).start()
LOOP.run_until_complete(_run_async())

View File

@ -0,0 +1,43 @@
from . import paths
import asyncio
import hashlib
import json
import os
def _hash_instance(instance: str):
# Get instance hash
hash = hashlib.sha3_256()
hash.update(instance.encode())
return hash.hexdigest()
async def list_installed_hashes(instance: str):
instance_hash = _hash_instance(instance)
archives = []
archives_proc = await asyncio.create_subprocess_exec([b"borg", b"list", b"--json-lines", paths.BACKUP_DIR.decode()], stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
result_code = await archives_proc.communicate()
if result_code == 0:
for i in filter(bool, archives_proc.stdout.splitlines()):
print("list: " + repr(i))
return archives
async def _gen_backup_system():
# TODO: check if exists
# Gen archive
gen_proc = await asyncio.create_subprocess_exec([b"borg", b"init", b"--json", b"--encryption", b"none", os.path.join(paths.INSTANCE_DIR, instance).decode()])
result = await gen_proc.communicate()
return result == 0
async def gen_backup_init(instance: str, hash: str, volumes: list):
# Gen backupsystem
await _gen_backup_system()
# Gen backup
name = ("install_" + _hash_instance(instance) + "_" + hash).lower()
archive_proc = await asyncio.create_subprocess_exec([b"borg", b"create", b"--json", b"--progress", b"-C", b"zstd,5", paths.BACKUP_DIR.decode() + b"::" + name.decode(), os.path.join(paths.INSTANCE_DIR, instance).decode()])
result = await archive_proc.communicate()
return result == 0

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkMenu" id="mainMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="mainMenuSettings">
<property name="label">gtk-preferences</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="mainMenuAbout">
<property name="label">gtk-dialog-info</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
<object class="GtkApplicationWindow" id="main_window">
<property name="width_request">800</property>
<property name="height_request">600</property>
<property name="can_focus">False</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="main_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Wine Starter</property>
<property name="subtitle" translatable="yes">&lt;Application&gt;</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkMenuButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="popup">mainMenu</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkStackSwitcher">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="stack">main_content</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="add_button">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="main_content">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;TO REPLACE&gt;</property>
</object>
<packing>
<property name="name">page0</property>
<property name="title" translatable="yes">page0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

101
winestarter/gui.py 100644
View File

@ -0,0 +1,101 @@
from . import instance
import os
from gi.repository import Gtk, Gio
def __loader_helper():
import gi
gi.require_version("Gtk", "3.0")
__loader_helper()
# Templates
_template_file = os.path.join(os.path.split(__file__)[0], "templates.glade")
@Gtk.Template(filename=_template_file)
class _StartWidget(Gtk.OffscreenWindow):
__gtype_name__ = "StartWidget"
template_type = "StartWidget"
__image = Gtk.Template.Child("start_image")
__name: Gtk.Label = Gtk.Template.Child("start_name")
__settings = Gtk.Template.Child("start_settings")
__run = Gtk.Template.Child("start_run")
__add_snapshot = Gtk.Template.Child("start_add_snapshot")
__snapshots = Gtk.Template.Child("start_snapshots")
def __init__(self, name, *args, **kargs):
super(*args, **kargs)
self.__name.set_label(name)
@Gtk.Template(filename=_template_file)
class _AboutDialog(Gtk.AboutDialog):
__gtype_name__ = "AboutDialog"
template_type = "AboutDialog"
# Application
class _Application(Gtk.Application):
_builder: Gtk.Builder
_window: Gtk.ApplicationWindow
_stack: Gtk.Stack
_configsSource: list
_instance: str
_configs: dict
_inited = None
def __init__(self, inited, configs: list = [], instance: str = None, *args, **kargs):
super().__init__(*args, application_id="de.marko10_000.WineStarter", **kargs)
self._configsSource = list(configs)
self._instance = str(instance)
self._inited = inited
def do_startup(self):
# Load configs
configs = self._configs = {}
for i in self._configsSource:
tmp = instance.gen_instance(i)
configs[tmp.get_instance()] = tmp
# Start gtk application
Gtk.Application.do_startup(self)
def do_activate(self):
# Load gui
self._builder = builder = Gtk.Builder()
builder.add_from_file(os.path.join(
os.path.split(__file__)[0], "gui.glade"))
# Gen gui
self._window = builder.get_object("main_window")
self._stack = builder.get_object("main_content")
if len(self._configs) == 1:
self._window.remove(self._window.get_child())
else:
for i in self._stack.get_children():
self._stack.remove(i)
# Add main window callbacks
def show_about(menu_entry):
_AboutDialog(parent=self._window).show_all()
builder.get_object("mainMenuAbout").connect("activate", show_about)
# Show main window
self._window.set_application(self)
self._window.show_all()
# Notify init end
self._inited()
app: _Application
def run_gui(inited=lambda: None, configs: list = None):
global app
app = _Application(inited=inited, configs=configs)
app.run()

View File

@ -0,0 +1,260 @@
import abc
import asyncio
import os
import yaml
from . import paths, wine
# Step
class Step(abc.ABC):
@abc.abstractclassmethod
async def run(self, wine, instance):
raise NotImplementedError()
@abc.abstractclassmethod
async def content_to_hash(self):
raise NotImplementedError()
class _StepPreInit(Step):
async def run(self, wine, instance):
pass
async def content_to_hash(self):
return b""
class _StepWineInit(Step):
async def run(self, wine, isinstance):
return await wine.run_command([b"wineboot", b"--init"], with_display=False)
# TODO: gen volumes
async def content_to_hash(self):
version = await asyncio.create_subprocess_exec(["wine", "--version"], stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
result = version.communicate()
if result != 0:
raise RuntimeError("Can't get wine version.")
return version.stdout.splitlines()[0]
class _StepExec(Step):
__command: list
__in_drive_c: bool
__cwd_path: str
def __init__(self, command, in_drive_c: bool = False, cwd_path: str = None):
self.__command = list(map(str.encode, command))
self.__in_drive_c = in_drive_c
self.__cwd_path = cwd_path
async def run(self, wine, instance):
tmp_cwd = self.__cwd_path
if self.__in_drive_c:
tmp_cwd = None
return await wine.run_command(command=self.__command, cwd_in_instance=self.__in_drive_c, cwd_path=tmp_cwd)
async def content_to_hash(self):
return b"\x00".join(self.__command)
# Instance management
class Instance():
__instance: str
__run: _StepExec
__wine: wine.WineConfig
__steps: list
__volumes: dict
def __init__(self, instance: str, run: _StepExec, steps: list = None, volumes: dict = {}):
if not instance:
raise ValueError("instance name can't be empty")
self.__instance = str(instance)
self.__run = run
self.__wine = wine.WineConfig(os.path.join(
paths.INSTANCE_DIR, instance), os.path.join(paths.FAKEHOME_DIR, instance))
self.__steps = list(steps)
self.__volumes = dict(volumes)
async def __run_install(self, name: str, step: Step, last_hash: bytes):
if not isinstance(step, Step):
raise ValueError("step have to be a Step.")
# TODO: Check if allready done
# TODO: Update gui (show install window)
# TODO: Update gui (show that step is running)
result = step.run(self.__wine, self)
# TODO: Create backup
async def install(self):
# TODO: Set gui information
last_hash = await self.__run_install("", _StepPreInit(), b"")
last_hash = await self.__run_install("init", _StepWineInit(), last_hash)
for name, step in self.__steps:
last_hash = await self.__run_install(name, step, last_hash)
def get_instance(self):
return self.__instance
async def run(self):
await self.install()
# TODO: Update gui (hide main window)
await self.__run.run(self.__wine, self)
# Create instance
def _get_relative_file(current_file, file):
return os.path.abspath(os.path.join(os.path.split(current_file)[0], file))
def _gen_run(file, config):
config = dict(config)
# Load command
if "command" not in config:
raise ValueError(
"Can't find command in exec in file " + repr(file) + ".")
if not isinstance(config["command"], list):
raise ValueError(
"Command in exec is not an array in file " + repr(file) + ".")
command = config["command"]
del config["command"]
# Check if use in_drive_c
in_drive_c = False
if "in_drive_c" in config:
if config["in_drive_c"]:
in_drive_c = True
del config["in_drive_c"]
# Check for unused arguments
if config:
raise ValueError("Unused attributes " +
", ".join(config.keys()) + " in file " + repr(file) + ".")
# Return new exec step
return _StepExec(command=command, in_drive_c=in_drive_c)
def _load_volumes(file, config):
# Precheck
if not isinstance(config, list):
raise ValueError(
"Volume list have to be a list in file " + repr(file) + ".")
# Volumes
volumes = []
for i in config:
# Check entry
if not isinstance(i, dict):
raise ValueError(
"Volume instance have to be an dict in file " + repr(file) + ".")
i = dict(i)
# Load name and path
if "name" not in i:
raise ValueError(
"Can't find name in volume info in file " + repr(file) + ".")
if "path" not in i:
raise ValueError(
"Can't find path in volume info in file " + repr(file) + ".")
name = str(i["name"])
path = str(i["path"])
del i["name"]
del i["path"]
volumes.append((name, path))
# Check for unused attributes
if i:
raise ValueError("Volume has unused attirbutes " +
", ".join(i.keys()) + " in file " + repr(file) + ".")
return volumes
def _load_module(file: str, content: dict):
# Check if string to load file
if isinstance(content, str):
file = _get_relative_file(file, content)
with open(file, "rb") as f:
content = yaml.load(f, Loader=yaml.SafeLoader)
content = dict(content)
# Load sub modules
result = []
volumes = []
if "modules" in content:
modules = content["modules"]
del content["modules"]
if not isinstance(modules, list):
raise ValueError(
"\"modules\" isn't a list in file " + repr(modules) + ".")
for i in modules:
tmp_result, tmp_volumes = _load_module(file, i)
result += tmp_result
volumes += tmp_volumes
# Load volumes
if "volumes" in content:
volumes += _load_volumes(file, content["volumes"])
del content["volumes"]
# Load name
if "name" not in content:
raise ValueError(
"name is missing in module in file " + repr(file) + ".")
name = content["name"]
del content["name"]
# Check if type exists
if "type" not in content:
raise ValueError("Can't find type in file " + repr(file) + ".")
type_name = content["type"]
del content["type"]
if type_name == "exec":
if tuple(content.keys()) != ("run",):
raise ValueError("Exec only supports \"run\" attribute but has " +
", ".join(content.keys()) + " in file " + repr(file) + ".")
result.append((name, _gen_run(file, content["run"])))
else:
raise ValueError("Type " + repr(type_name) +
" is unknown in file " + repr(file) + ".")
# Cleanup modules
return result, volumes
def gen_instance(file):
# Load base data
with open(file, "rb") as f:
content = dict(yaml.load(f, Loader=yaml.SafeLoader))
if "name" not in content:
raise ValueError("Can't find name inside of " + repr(file) + ".")
name = content["name"]
del content["name"]
# Load run
if "run" not in content:
raise ValueError("Can't find run inside of " + repr(file) + ".")
run = _gen_run(file, content["run"])
del content["run"]
# Load modules
modules = []
volumes = []
if "modules" not in content:
raise ValueError("Can't find \"modules\" in " + repr(file) + ".")
if not isinstance(content["modules"], list):
raise ValueError(
"\"modules\" have to be an array in file " + repr(file) + ".")
for i in content["modules"]:
tmp_modules, tmp_volumes = _load_module(file, i)
modules += tmp_modules
volumes += tmp_volumes
del content["modules"]
# Check unused attributes
if content:
raise ValueError("Unused attributes " +
", ".join(content.keys()) + " in file " + repr(file) + ".")
# Return new instance
return Instance(instance=name, run=run, steps=modules, volumes=dict(volumes))

View File

@ -0,0 +1,77 @@
import os
# Find dirs
INSTANCE_DIR = None
FAKEHOME_DIR = None
CONFIG_DIR = None
BACKUP_DIR = None
DOWNLOADS_DIR = None
if os.environ.get("XDG_CACHE_HOME", None) is not None:
# Check if in flatpak
if os.environ.get("FLATPAK_ID", None) is None:
tmp = os.path.join(os.environ.get("XDG_CACHE_HOME"), "winestarter")
else:
tmp = os.environ.get("XDG_CACHE_HOME")
# Set paths for backup and downloads
if BACKUP_DIR is None:
BACKUP_DIR = os.path.join(tmp, "backups")
if DOWNLOADS_DIR is None:
DOWNLOADS_DIR = os.path.join(tmp, "downloads")
del tmp
if os.environ.get("XDG_CONFIG_HOME", None) is not None:
# Check if in flatpak
if os.environ.get("FLATPAK_ID", None) is None:
tmp = os.path.join(os.environ.get("XDG_CONFIG_HOME"), "winestarter")
else:
tmp = os.environ.get("XDG_CONFIG_HOME")
# Set paths for config dir
if CONFIG_DIR is None:
CONFIG_DIR = os.path.join(tmp, "config")
del tmp
if os.environ.get("XDG_DATA_HOME", None) is not None:
# Check if in flatpak
if os.environ.get("FLATPAK_ID", None) is None:
tmp = os.path.join(os.environ.get("XDG_DATA_HOME"), "winestarter")
else:
tmp = os.environ.get("XDG_DATA_HOME")
# Set paths for instances, fakehomes, config, backups and downloads
if INSTANCE_DIR is None:
INSTANCE_DIR = os.path.join(tmp, "instances")
if FAKEHOME_DIR is None:
FAKEHOME_DIR = os.path.join(tmp, "fakehomes")
if CONFIG_DIR is None:
CONFIG_DIR = os.path.join(tmp, "config")
if BACKUP_DIR is None:
BACKUP_DIR = os.path.join(tmp, "backups")
if DOWNLOADS_DIR is None:
DOWNLOADS_DIR = os.path.join(tmp, "downloads")
del tmp
if os.environ.get("HOME", None):
# As backup the home dir
tmp = os.path.join(os.environ.get("HOME"), ".winestarter")
if INSTANCE_DIR is None:
INSTANCE_DIR = os.path.join(tmp, "instances")
if FAKEHOME_DIR is None:
FAKEHOME_DIR = os.path.join(tmp, "fakehomes")
if CONFIG_DIR is None:
CONFIG_DIR = os.path.join(tmp, "config")
if BACKUP_DIR is None:
BACKUP_DIR = os.path.join(tmp, "backups")
if DOWNLOADS_DIR is None:
DOWNLOADS_DIR = os.path.join(tmp, "downloads")
del tmp
if None in (INSTANCE_DIR, FAKEHOME_DIR, CONFIG_DIR, BACKUP_DIR, DOWNLOADS_DIR):
print(repr((INSTANCE_DIR, FAKEHOME_DIR, CONFIG_DIR, BACKUP_DIR, DOWNLOADS_DIR)))
raise ValueError("Can't find HOME or XDG_DATA_HOME")
# Create paths
os.makedirs(INSTANCE_DIR, exist_ok=True)
os.makedirs(FAKEHOME_DIR, exist_ok=True)
os.makedirs(CONFIG_DIR, exist_ok=True)
os.makedirs(BACKUP_DIR, exist_ok=True)
os.makedirs(DOWNLOADS_DIR, exist_ok=True)

View File

@ -0,0 +1,391 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="AboutDialog" parent="GtkAboutDialog">
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="type_hint">dialog</property>
<property name="urgency_hint">True</property>
<property name="program_name">Wine Starter</property>
<property name="license" translatable="yes">Dieses Programm kommt OHNE JEDWEDE GARANTIE.
Besuchen Sie &lt;a href="https://www.gnu.org/licenses/agpl-3.0.html"&gt;GNU Affero General Public License, Version 3 oder neuer&lt;/a&gt; für weitere Informationen.</property>
<property name="logo_icon_name">image-missing</property>
<property name="wrap_license">True</property>
<property name="license_type">agpl-3-0</property>
<child type="titlebar">
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</template>
<object class="GtkOffscreenWindow" id="StartWidget">
<property name="can_focus">False</property>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkImage" id="start_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">32</property>
<property name="stock">gtk-cdrom</property>
<property name="icon_size">6</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="start_name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;Application&gt;</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="start_settings">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="start_run">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_top">8</property>
<property name="always_show_image">True</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-play</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">RUN</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="width_request">250</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">32</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">1</property>
<property name="xscale">0</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Snapshots:</property>
<property name="ellipsize">start</property>
<property name="single_line_mode">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="start_add_snapshot">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-add</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkListBox" id="start_snapshots">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkOffscreenWindow" id="install">
<property name="can_focus">False</property>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="app_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-cdrom</property>
<property name="icon_size">6</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="app_name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;Application&gt;</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkListBox" id="app_steps">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

100
winestarter/wine.py 100644
View File

@ -0,0 +1,100 @@
import asyncio
import os
class _Result():
stdout = str
stderr = str
result = int
def __init__(self, result, stdout, stderr):
# Check values
if not isinstance(result, int):
raise ValueError("result have to be an int.")
if not isinstance(stdout, str):
raise ValueError("stdout have to be an string.")
if not isinstance(stderr, str):
raise ValueError("stderr have to be an string.")
# Set values
self.stdout = stdout
self.stderr = stderr
self.result = result
def __bool__(self):
return self.result == 0
class _RunCommand():
__args = list
__envs = dict
__cwd: str
def __init__(self, args, envs):
self.__args = args
self.__envs = envs
self.__cwd = None
@property
def cwd(self):
return self.__cwd
@cwd.setter
def cwd(self, cwd: str):
if self.__cwd is not None:
raise RuntimeError("change dir can be done onle ones.")
if not isinstance(cwd, str):
raise ValueError("change dir have to be a string.")
self.__cwd = cwd
async def run(self):
proc = await asyncio.create_subprocess_exec(self.__args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=self.__envs, cwd=self.__cwd)
result_code = await proc.communicate()
return _Result(result_code, proc.stdout.decode(), proc.stderr.decode())
class WineConfig():
path = str
home = str
def __init__(self, path, home):
# Check values
if not isinstance(path, str):
raise ValueError("path have to be a string.")
if not isinstance(home, str):
raise ValueError("home have to be a string.")
# Set values
self.path = path
self.home = home
def preapre_command(self, args, envs={}, with_display=True, cwd_in_instance: bool = False, cwd_path: str = None):
envs = dict(envs)
# Set base env
envs["HOME"] = self.home
envs["WINEPREFIX"] = self.path
# Check if display have to remove
if not with_display:
envs["DISPLAY"] = ""
envs["WAYLAND_DISPLAY"] = ""
# Create prepared command
result = _RunCommand(args, envs)
# Change dir into instance
if cwd_in_instance:
result.cwd = os.path.join(self.path, "drive_c")
if cwd_path:
if cwd_in_instance:
raise ValueError(
"cwd_path and cwd_in_instance can't be set at the same time.")
result.cwd = cwd_path
# Return result
return result
async def run_command(self, *args, **kargs):
cmd = self.preapre_command(*args, **kargs)
return await cmd.run()