Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
Marko Semet | 2a8d393d43 | |
Marko Semet | 52b61aafaa | |
Marko Semet | 6c2e2661c3 | |
Marko Semet | 5df2e21217 | |
Marko Semet | 6b4da35bae | |
Marko Semet | a64926a7ef | |
Marko Semet | 1684c9f83f | |
Marko Semet | efe98de6f4 | |
Marko Semet | f9504a9605 |
|
@ -129,3 +129,4 @@ dmypy.json
|
|||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
*.glade~
|
|
@ -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"]
|
||||
)
|
|
@ -0,0 +1,53 @@
|
|||
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()
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
# Run sets
|
||||
try:
|
||||
await gui.app.update_instances()
|
||||
except:
|
||||
gui.app.quit()
|
||||
raise
|
||||
|
||||
# Wait for async ends
|
||||
while not _terminate.acquire(blocking=False):
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
def _run_gui():
|
||||
def inited_func():
|
||||
_terminate.release()
|
||||
try:
|
||||
gui.run_gui(asyncloop=LOOP, inited=inited_func, configs=list(
|
||||
map(lambda x: x[0], args.configs)))
|
||||
finally:
|
||||
_terminate.release()
|
||||
|
||||
|
||||
threading.Thread(target=_run_gui).start()
|
||||
LOOP.run_until_complete(_run_async())
|
|
@ -0,0 +1,68 @@
|
|||
from . import paths
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
class ConfigAndBackup():
|
||||
__instance = None
|
||||
|
||||
def _hash_instance(self):
|
||||
hash = hashlib.sha3_256()
|
||||
hash.update(self.__instance.get_instance().encode())
|
||||
return hash.hexdigest()
|
||||
|
||||
async def _list_installed_hashes(self):
|
||||
# List archives
|
||||
archives_proc = await asyncio.create_subprocess_exec(b"borg", b"list", b"--json", paths.BACKUP_DIR.encode(), stdout=asyncio.subprocess.PIPE)
|
||||
stdout, stderr = await archives_proc.communicate()
|
||||
if archives_proc.returncode != 0:
|
||||
raise RuntimeError("Borg crashed.")
|
||||
archives = []
|
||||
for i in json.loads(stdout)["archives"]:
|
||||
archives.append(i["name"])
|
||||
|
||||
# Filter own
|
||||
instance_hash = self._hash_instance()
|
||||
return list(map(lambda x: x[len(instance_hash) + 1:], filter(lambda x: x.startswith(instance_hash), archives)))
|
||||
|
||||
async def _run_backup(self, name: bytes, *args, path:bytes=None):
|
||||
# Check if exists (when true delete it)
|
||||
archive_name = b"%s::%s" % (paths.BACKUP_DIR.encode(), name)
|
||||
if name in await self._list_installed_hashes():
|
||||
process = await asyncio.create_subprocess_exec(b"borg", b"delete", archive_name)
|
||||
await process.wait()
|
||||
if process.returncode != 0:
|
||||
raise RuntimeError("Backup can't be created.")
|
||||
|
||||
# Create backup
|
||||
if path is None:
|
||||
path = self.__instance.get_instance_path().encode()
|
||||
process = await asyncio.create_subprocess_exec(b"borg", b"create", b"-C", b"zstd,5", archive_name, *args, cwd=path)
|
||||
await process.wait()
|
||||
if process.returncode != 0:
|
||||
raise RuntimeError("Backup can't be created.")
|
||||
|
||||
def __init__(self, instance):
|
||||
# Set values
|
||||
self.__instance = instance
|
||||
|
||||
async def init(self):
|
||||
# Check borg
|
||||
process = await asyncio.create_subprocess_exec(b"borg", b"--version", stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL)
|
||||
await process.wait()
|
||||
if process.returncode != 0:
|
||||
raise RuntimeError("Can't find borg backup.")
|
||||
|
||||
# Create archive
|
||||
process = await asyncio.create_subprocess_exec(b"borg", b"info", paths.BACKUP_DIR.encode(), stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL)
|
||||
await process.wait()
|
||||
if process.returncode != 0:
|
||||
process = await asyncio.create_subprocess_exec(b"borg", b"init", b"-e", b"none", paths.BACKUP_DIR.encode(), stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL)
|
||||
await process.wait()
|
||||
if process.returncode != 0:
|
||||
raise RuntimeError("Borg repo is broken.")
|
||||
|
||||
async def gen_install_backup(self, name:str):
|
||||
await self._run_backup(b"install_%s" + (name.encode(),), b".")
|
|
@ -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"><Application></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"><TO REPLACE></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>
|
|
@ -0,0 +1,150 @@
|
|||
from . import instance
|
||||
import os
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio
|
||||
import asyncio
|
||||
|
||||
|
||||
# Templates
|
||||
_template_path = os.path.join(os.path.split(__file__)[0], "templates")
|
||||
|
||||
|
||||
@Gtk.Template(filename=os.path.join(_template_path, "StartWidget.glade"))
|
||||
class _StartWidget(Gtk.Box):
|
||||
__gtype_name__ = "StartWidget"
|
||||
|
||||
__image = Gtk.Template.Child(name="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")
|
||||
|
||||
__instance:instance.Instance
|
||||
__aplication = None
|
||||
|
||||
def __init__(self, application, instance:instance.Instance, *args, **kargs):
|
||||
# Init template
|
||||
super().__init__(*args, **kargs)
|
||||
self.__instance = instance
|
||||
self.__aplication = application
|
||||
|
||||
# Set values
|
||||
self.__name.set_label(instance.get_instance())
|
||||
|
||||
# Run callback
|
||||
self.__run.connect("clicked", self.__run_clicked)
|
||||
|
||||
def __run_clicked(self, button):
|
||||
self.__aplication.run_async(self.__instance.run(), wait=False)
|
||||
|
||||
|
||||
|
||||
@Gtk.Template(filename=os.path.join(_template_path, "AboutDialog.glade"))
|
||||
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
|
||||
_configs_box:dict
|
||||
_inited = None
|
||||
_asyncloop = None
|
||||
|
||||
def __init__(self, asyncloop, 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
|
||||
self._asyncloop = asyncloop
|
||||
|
||||
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")
|
||||
self._configs_box = instance_box = {}
|
||||
if len(self._configs) == 1:
|
||||
self._window.remove(self._window.get_child())
|
||||
tmp = Gtk.Box()
|
||||
tmp.show()
|
||||
self._window.add(tmp)
|
||||
instance_box[list(self._configs.keys())[0]] = tmp
|
||||
else:
|
||||
for i in self._stack.get_children():
|
||||
self._stack.remove(i)
|
||||
raise NotImplementedError()
|
||||
|
||||
# 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()
|
||||
|
||||
def _set_instance_ui(self, instance:str, gui):
|
||||
# Clean box
|
||||
box = self._configs_box[instance]
|
||||
for i in box.get_children():
|
||||
box.remove(i)
|
||||
|
||||
# Set item
|
||||
box.pack_start(gui, True, True, 0)
|
||||
gui.show_all()
|
||||
|
||||
async def update_instances(self, start:str=None):
|
||||
# Load gui for instances
|
||||
for iID, i in self._configs.items():
|
||||
tmp = _StartWidget(self, i)
|
||||
self._set_instance_ui(iID, tmp)
|
||||
tmp.show_all()
|
||||
|
||||
# Start
|
||||
if start is not None:
|
||||
raise NotImplementedError() # TODO
|
||||
|
||||
def run_async(self, command, wait:bool=True):
|
||||
|
||||
# Wait
|
||||
if wait:
|
||||
asyncio.run_coroutine_threadsafe(command, self._asyncloop)
|
||||
else:
|
||||
async def runner():
|
||||
self._asyncloop.create_task(command)
|
||||
asyncio.run_coroutine_threadsafe(runner(), self._asyncloop)
|
||||
|
||||
|
||||
app: _Application
|
||||
|
||||
|
||||
def run_gui(asyncloop, inited=lambda: None, configs: list = None):
|
||||
global app
|
||||
app = _Application(inited=inited, configs=configs, asyncloop=asyncloop)
|
||||
app.run()
|
|
@ -0,0 +1,323 @@
|
|||
import abc
|
||||
import asyncio
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import yaml
|
||||
from . import paths, wine, config
|
||||
|
||||
|
||||
# Step
|
||||
class Step(abc.ABC):
|
||||
@abc.abstractclassmethod
|
||||
async def run(self, wine, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractclassmethod
|
||||
async def content_to_hash(self, wine, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class _StepPreInit(Step):
|
||||
def _find_drive_c(self, wine):
|
||||
return os.path.join(os.path.abspath(wine.path), "drive_c")
|
||||
|
||||
def _gen_paths(self, wine, instance):
|
||||
# Find paths
|
||||
drive_c = self._find_drive_c(wine)
|
||||
volume_dir = os.path.abspath(os.path.join(paths.VOLUME_DIR, instance.get_instance()))
|
||||
|
||||
# Iterate
|
||||
for volume, path in instance.get_volumes().items():
|
||||
if "\x00" in volume or "/" in volume or "\\" in volume:
|
||||
raise ValueError("Volume name contains slash or backslash")
|
||||
voldir = os.path.join(volume_dir, volume)
|
||||
pathdir = os.path.join(drive_c, path)
|
||||
yield (voldir, pathdir)
|
||||
|
||||
async def run(self, wine:wine.WineConfig, instance):
|
||||
# Reset path
|
||||
shutil.rmtree(wine.path)
|
||||
os.makedirs(self._find_drive_c(wine), exist_ok=True)
|
||||
|
||||
# Create volumes
|
||||
for volume, path in self._gen_paths(wine, instance):
|
||||
os.makedirs(volume, exist_ok=True)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
os.symlink(os.path.relpath(volume, os.path.dirname(path)), path)
|
||||
|
||||
async def content_to_hash(self, wine, instance):
|
||||
return b"\x00".join(map(lambda x: b"%s\x00%s" % x, map(lambda x: (x[0].encode("UTF-8"), x[1].encode("UTF-8")), sorted(self._gen_paths(wine, instance)))))
|
||||
|
||||
|
||||
class _StepWineInit(Step):
|
||||
async def run(self, wine, isinstance):
|
||||
result = await wine.run_command([b"wineboot", b"--init"], with_display=False)
|
||||
if not result:
|
||||
raise RuntimeError() # TODO: gen exception + handling
|
||||
return result
|
||||
|
||||
async def content_to_hash(self, wine, instance):
|
||||
version = await asyncio.create_subprocess_exec(b"wine", b"--version", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||
stdout, stderr = await version.communicate()
|
||||
if version.returncode != 0:
|
||||
raise RuntimeError("Can't get wine version.")
|
||||
return 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
|
||||
result = await wine.run_command(args=self.__command, cwd_in_instance=self.__in_drive_c, cwd_path=tmp_cwd)
|
||||
if not result:
|
||||
print(repr(result))
|
||||
raise RuntimeError() # TODO: Exception
|
||||
return result
|
||||
|
||||
async def content_to_hash(self, wine, isinstance):
|
||||
return b"\x00".join(self.__command)
|
||||
|
||||
|
||||
# Instance management
|
||||
class Instance():
|
||||
__instance: str
|
||||
__run: _StepExec
|
||||
__wine: wine.WineConfig
|
||||
__steps: list
|
||||
__volumes: dict
|
||||
__backup: config.ConfigAndBackup
|
||||
|
||||
__has_to_restore_backup: bool
|
||||
|
||||
def __gen_hash(self, last_hash:bytes, name:str, data:bytes):
|
||||
hasher = hashlib.sha3_256()
|
||||
hasher.update(last_hash)
|
||||
hasher.update(name.encode("UTF-8"))
|
||||
hasher.update(data)
|
||||
return hasher.hexdigest()
|
||||
|
||||
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)
|
||||
self.__has_to_restore_backup = True
|
||||
|
||||
async def __run_install(self, name: str, step: Step, last_hash: bytes, stop_wine:bool=True):
|
||||
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 = await step.run(self.__wine, self)
|
||||
if stop_wine:
|
||||
await self.__wine.stop_wine()
|
||||
|
||||
# Gen backup and return
|
||||
next_hash = self.__gen_hash(last_hash, name, await step.content_to_hash(self.__wine, self))
|
||||
await self.__backup.gen_install_backup(next_hash)
|
||||
return next_hash.encode("UTF-8")
|
||||
|
||||
async def install(self):
|
||||
# TODO: Set gui information
|
||||
last_hash = await self.__run_install("", _StepPreInit(), b"", stop_wine=False)
|
||||
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
|
||||
|
||||
def get_volumes(self):
|
||||
return dict(self.__volumes)
|
||||
|
||||
def get_instance_path(self):
|
||||
return self.__wine.path
|
||||
|
||||
async def is_installed(self):
|
||||
return False # TODO
|
||||
|
||||
async def run(self):
|
||||
# Create backup
|
||||
self.__backup = config.ConfigAndBackup(self)
|
||||
await self.__backup.init()
|
||||
|
||||
# Install and run
|
||||
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 = bool(config["in_drive_c"])
|
||||
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, cwd_path=os.path.dirname(file))
|
||||
|
||||
|
||||
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))
|
|
@ -0,0 +1,89 @@
|
|||
import os
|
||||
|
||||
|
||||
# Find dirs
|
||||
INSTANCE_DIR = None
|
||||
FAKEHOME_DIR = None
|
||||
CONFIG_DIR = None
|
||||
BACKUP_DIR = None
|
||||
DOWNLOADS_DIR = None
|
||||
VOLUME_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")
|
||||
if VOLUME_DIR is None:
|
||||
VOLUME_DIR = os.path.join(tmp, "volumes")
|
||||
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")
|
||||
if VOLUME_DIR is None:
|
||||
VOLUME_DIR = os.path.join(tmp, "volumes")
|
||||
del tmp
|
||||
if None in (INSTANCE_DIR, FAKEHOME_DIR, CONFIG_DIR, BACKUP_DIR, DOWNLOADS_DIR, VOLUME_DIR):
|
||||
print(repr((INSTANCE_DIR, FAKEHOME_DIR, CONFIG_DIR, BACKUP_DIR, DOWNLOADS_DIR, VOLUME_DIR)))
|
||||
raise ValueError("Can't find HOME or XDG_DATA_HOME")
|
||||
|
||||
|
||||
# Create paths
|
||||
INSTANCE_DIR = os.path.abspath(INSTANCE_DIR)
|
||||
FAKEHOME_DIR = os.path.abspath(FAKEHOME_DIR)
|
||||
CONFIG_DIR = os.path.abspath(CONFIG_DIR)
|
||||
BACKUP_DIR = os.path.abspath(BACKUP_DIR)
|
||||
DOWNLOADS_DIR = os.path.abspath(DOWNLOADS_DIR)
|
||||
VOLUME_DIR = os.path.abspath(VOLUME_DIR)
|
||||
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)
|
||||
os.makedirs(VOLUME_DIR, exist_ok=True)
|
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<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"><Application></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>
|
|
@ -0,0 +1,47 @@
|
|||
<?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 <a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, Version 3 oder neuer</a> 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>
|
||||
</interface>
|
|
@ -0,0 +1,265 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="StartWidget" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<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"><Application></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>
|
||||
<property name="direction">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>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,119 @@
|
|||
import asyncio
|
||||
import os
|
||||
|
||||
|
||||
class _Result():
|
||||
stdout = str
|
||||
stderr = str
|
||||
result = int
|
||||
|
||||
def __init__(self, result, stdout, stderr, is_wine:bool=False):
|
||||
# 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
|
||||
if is_wine:
|
||||
lines = stderr.splitlines(keepends=True)
|
||||
if len(lines) == 1 and lines[0].startswith("wine: cannot find '") and lines[0].endswith("'\n"):
|
||||
self.result = -1
|
||||
else:
|
||||
self.result = result
|
||||
else:
|
||||
self.result = result
|
||||
|
||||
def __bool__(self):
|
||||
return self.result == 0
|
||||
|
||||
def __repr__(self):
|
||||
return "<Wine-Result result=" + str(self.result) + " stdout=" + repr(self.stdout) + " stderr=" + repr(self.stderr) + ">"
|
||||
|
||||
|
||||
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):
|
||||
# Copy own env
|
||||
env = dict(os.environ)
|
||||
for iID, i in self.__envs.items():
|
||||
env[iID] = i
|
||||
|
||||
# Run proc
|
||||
proc = await asyncio.create_subprocess_exec(*(self.__args), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=env, cwd=self.__cwd)
|
||||
std_out, std_err = await proc.communicate()
|
||||
return _Result(proc.returncode, std_out.decode(), std_err.decode(), is_wine=self.__args[0] == b"wine")
|
||||
|
||||
|
||||
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()
|
||||
|
||||
async def stop_wine(self):
|
||||
return await self.run_command([b"wineserver", b"-w"])
|
Loading…
Reference in New Issue