- rewrite from scratch
- modularity thanks to customizable recipes
main
Mirko 2 years ago
parent 1a835b2e1a
commit cbf5c9a9d6

@ -29,4 +29,9 @@ sudo ninja -C build install
## Run
```bash
vanilla-first-setup
```
```
### Using custom recipes
Place a new recipe in `/etc/vanilla-first-setup/recipe.json` or launch the
utility with the `VANILLA_CUSTOM_RECIPE` environment variable set to the path
of the recipe.

@ -1 +1 @@
0.1.9
1.0.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

7
debian/changelog vendored

@ -1,5 +1,6 @@
vanilla-first-setup (0.1.9) jammy; urgency=low
vanilla-first-setup (1.0.0) jammy; urgency=low
* First release
* Rewrite from scratch to support custom recipes and
to be more modular.
-- Mirko Brombin <send@mirko.pm> Mon, 05 Sep 2022 20:42:00 +0000
-- Mirko Brombin <send@mirko.pm> Thu, 20 Sep 2022 22:01:00 +0000

@ -1,5 +1,5 @@
project('io.github.vanilla-os.FirstSetup',
version: '0.1.9',
version: '1.0.0',
meson_version: '>= 0.59.0',
default_options: [ 'warning_level=2',
'werror=false',

@ -0,0 +1,136 @@
{
"log_file": "/etc/vanilla/first-setup.log",
"distro_name": "Vanilla OS",
"distro_logo": "io.github.vanilla-os.FirstSetup",
"steps": {
"welcome": {
"template": "welcome"
},
"theme": {
"template": "theme"
},
"packages": {
"template": "preferences",
"icon": "vanilla-package-symbolic",
"title": "Package Manager",
"description": "Choose one or more package managers to install",
"preferences": [
{
"id": "flatpak",
"title": "Flatpak",
"subtitle": "Will also configure the Flathub repository.",
"default": true
},
{
"id": "snap",
"title": "Snap",
"subtitle": "Uses the Snapcraft repository. Default in Ubuntu."
},
{
"id": "appimage",
"title": "AppImage",
"subtitle": "Will install the necessary dependencies to run AppImages."
}
],
"final": [
{
"if": "flatpak",
"type": "command",
"commands": [
"sudo apt install -y flatpak",
"flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo"
]
},
{
"if": "snap",
"type": "command",
"commands": ["sudo apt install -y snapd"]
},
{
"if": "appimage",
"type": "command",
"commands": ["sudo apt install -y fuse2"]
}
]
},
"subsystem": {
"template": "yes-no",
"icon": "vanilla-container-terminal-symbolic",
"title": "Sub System",
"description": "Access a minimal mutable Ubuntu installation integrated with Vanilla OS.",
"buttons": {
"yes": "Enable",
"no": "Skip",
"info": {
"type": "text",
"title": "What is Sub System?",
"text": "The Sub-System is a container that allows you to install deb packages without altering the system. It is useful for installing software without having to enter in read-write mode.\n\nYou don't need to enter in the container to install packages, just use the apx command (wrapper around the apt inside the container) to install new programs and automatically make them available in your Vanilla OS installation.\n\nThis features uses distrobox as backend."
}
},
"final": [
{
"if": "subsystem",
"type": "command",
"commands": [
"sudo apt install -y apx",
"curl -s https://raw.githubusercontent.com/89luca89/distrobox/main/install | sudo sh"
]
}
]
},
"nvidia": {
"template": "yes-no",
"icon": "video-display-symbolic",
"title": "NVIDIA® Drivers",
"description": "Choose whether to install proprietary NVIDIA drivers for better compatibility and performance.",
"buttons": {
"yes": "Yes, install",
"no": "Skip",
"info": {
"type": "text",
"title": "About Proprietary Drivers",
"text": "A proprietary driver has private code that neither Vanilla OS nor Ubuntu developers can't review.\n\nSecurity and other updates are dependent on the driver vendor."
}
},
"final": [
{
"if": "nvidia",
"type": "command",
"commands": ["sudo ubuntu-drivers install --recommended"]
}
]
},
"extra": {
"template": "preferences",
"icon": "vanilla-puzzle-piece-symbolic",
"title": "Extra Settings",
"description": "The following are optional settings, leave them as they are if you don't know what they do.",
"preferences": [
{
"id": "apport",
"title": "Apport",
"subtitle": "Apport is a crash reporting system that helps us improve the stability of the system."
}
],
"final": [
{
"if": "apport",
"type": "command",
"commands": [
"sudo apt install -y apport",
"systemctl enable apport.service || true"
]
},
{
"if": "apport",
"condition": false,
"type": "command",
"commands": [
"sudo apt remove -y apport",
"systemctl disable apport.service || true"
]
}
]
}
}
}

@ -0,0 +1,10 @@
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
defaultsdir = join_paths(pkgdatadir, 'vanilla_first_setup/defaults')
sources = [
'__init__.py',
'welcome.py',
'theme.py',
]
install_data(sources, install_dir: defaultsdir)

@ -0,0 +1,54 @@
# theme.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
from gi.repository import Gtk, Gio, GLib, Adw
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/default-theme.ui')
class VanillaDefaultTheme(Gtk.Box):
__gtype_name__ = 'VanillaDefaultTheme'
btn_next = Gtk.Template.Child()
btn_default = Gtk.Template.Child()
btn_dark = Gtk.Template.Child()
def __init__(self, window, distro_info, key, step, **kwargs):
super().__init__(**kwargs)
self.__window = window
self.__distro_info = distro_info
self.__key = key
self.__step = step
self.__build_ui()
self.btn_next.connect("clicked", self.__window.next)
self.btn_default.connect('toggled', self.__set_theme, "light")
self.btn_dark.connect('toggled', self.__set_theme, "dark")
def __build_ui(self):
self.btn_dark.set_group(self.btn_default)
def __set_theme(self, widget, theme: str):
pref = "prefer-dark" if theme == "dark" else "default"
gtk = "Adwaita-dark" if theme == "dark" else "Adwaita"
Gio.Settings.new("org.gnome.desktop.interface").set_string(
"color-scheme", pref)
Gio.Settings.new("org.gnome.desktop.interface").set_string(
"gtk-theme", gtk)
def get_finals(self):
return {}

@ -0,0 +1,93 @@
# welcome.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
from gi.repository import Gtk, Gio, GLib, Adw
from vanilla_first_setup.utils.run_async import RunAsync
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/default-welcome.ui')
class VanillaDefaultWelcome(Adw.Bin):
__gtype_name__ = 'VanillaDefaultWelcome'
btn_next = Gtk.Template.Child()
status_page = Gtk.Template.Child()
welcome = [
'Welcome',
'Benvenuto',
'Bienvenido',
'Bienvenue',
'Willkommen',
'Bem-vindo',
'Добро пожаловать',
'欢迎',
'ようこそ',
'환영합니다',
'أهلا بك',
'ברוך הבא',
'Καλώς ήρθατε',
'Hoşgeldiniz',
'Welkom',
'Witamy',
'Välkommen',
'Tervetuloa',
'Vítejte',
'Üdvözöljük',
'Bun venit',
'Vitajte',
'Tere tulemast',
'Sveiki atvykę',
'Dobrodošli',
'خوش آمدید',
'आपका स्वागत है',
'স্বাগতম',
'வரவேற்கிறோம்',
'స్వాగతం',
'मुबारक हो',
'સુસ્વાગત છે',
'ಸುಸ್ವಾಗತ',
'സ്വാഗതം'
]
def __init__(self, window, distro_info, key, step, **kwargs):
super().__init__(**kwargs)
self.__window = window
self.__distro_info = distro_info
self.__key = key
self.__step = step
# animation start
self.__start_welcome_animation()
# signals
self.btn_next.connect("clicked", self.__window.next)
# set distro logo
self.status_page.set_icon_name(self.__distro_info["logo"])
def __start_welcome_animation(self):
def change_langs():
while True:
for lang in self.welcome:
GLib.idle_add(self.status_page.set_title, lang)
time.sleep(1.2)
RunAsync(change_langs, None)
def get_finals(self):
return {}

@ -1,4 +1,4 @@
# prop_nvidia.py
# dialog.py
#
# Copyright 2022 mirkobrombin
#
@ -17,10 +17,14 @@
from gi.repository import Gtk, Adw
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/dialog-prop-nvidia.ui')
class ProprietaryDriverDialog(Adw.Window):
__gtype_name__ = 'ProprietaryDriverDialog'
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/dialog.ui')
class VanillaDialog(Adw.Window):
__gtype_name__ = 'VanillaDialog'
def __init__(self, window, **kwargs):
label_text = Gtk.Template.Child()
def __init__(self, window, title, text, **kwargs):
super().__init__(**kwargs)
self.set_transient_for(window)
self.set_title(title)
self.label_text.set_text(text)

@ -1,10 +0,0 @@
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
dialogsdir = join_paths(pkgdatadir, 'vanilla_first_setup/dialogs')
sources = [
'__init__.py',
'subsystem.py',
'prop_drivers.py',
]
install_data(sources, install_dir: dialogsdir)

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0" />
<template class="VanillaDefaultTheme" parent="GtkBox">
<property name="orientation">vertical</property>
<property name="halign">fill</property>
<property name="valign">center</property>
<property name="hexpand">true</property>
<child>
<object class="GtkBox">
<property name="valign">center</property>
<property name="spacing">10</property>
<property name="halign">center</property>
<child>
<object class="GtkCheckButton" id="btn_default">
<property name="tooltip-text">Default</property>
<property name="halign">center</property>
<property name="active">True</property>
<style>
<class name="theme-selector" />
<class name="light" />
<class name="card" />
</style>
</object>
</child>
<child>
<object class="GtkCheckButton" id="btn_dark">
<property name="tooltip-text">Dark</property>
<property name="halign">center</property>
<style>
<class name="theme-selector" />
<class name="dark" />
<class name="card" />
</style>
</object>
</child>
</object>
</child>
<child>
<object class="AdwStatusPage">
<property name="title">Color Scheme</property>
<property name="description">Choose a color scheme for your system.</property>
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="GtkButton" id="btn_next">
<property name="label">Next</property>
<property name="halign">center</property>
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
</object>
</child>
</object>
</child>
</template>
</interface>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0" />
<template class="VanillaDefaultWelcome" parent="AdwBin">
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="AdwStatusPage" id="status_page">
<property name="icon-name">io.github.vanilla-os.FirstSetup</property>
<property name="title">Welcome!</property>
<property name="description">Make your choices, this wizard will take care of everything.</property>
<child>
<object class="GtkButton" id="btn_next">
<property name="label">Let's Start</property>
<property name="halign">center</property>
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
</object>
</child>
</object>
</child>
</template>
</interface>

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<template class="SubSystemDialog" parent="AdwWindow">
<property name="title" translatable="yes">Info About Sub System</property>
<property name="default-width">500</property>
<property name="modal">True</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar">
<style>
<class name="flat"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="margin-top">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-bottom">20</property>
<property name="wrap">True</property>
<property name="label">The Sub-System is a container that allows you to install deb packages without altering the system. It is useful for installing software without having to enter in read-write mode.
You don't need to enter in the container to install packages, just use the apx command (wrapper around the apt inside the container) to install new programs and automatically make them available in your Vanilla OS installation.
This features uses distrobox as backend.</property>
</object>
</child>
</object>
</child>
</template>
</interface>

@ -2,8 +2,8 @@
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<template class="ProprietaryDriverDialog" parent="AdwWindow">
<property name="title" translatable="yes">Info About Proprietary Drivers</property>
<template class="VanillaDialog" parent="AdwWindow">
<property name="title" translatable="yes">Showing Informations</property>
<property name="default-width">500</property>
<property name="modal">True</property>
<child>
@ -17,15 +17,12 @@
</object>
</child>
<child>
<object class="GtkLabel">
<object class="GtkLabel" id="label_text">
<property name="margin-top">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-bottom">20</property>
<property name="wrap">True</property>
<property name="label">A proprietary driver has private code that neither Vanilla OS nor Ubuntu developers can't review.
Security and other updates are dependent on the driver vendor.</property>
</object>
</child>
</object>

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0" />
<template class="VanillaDone" parent="AdwBin">
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="AdwStatusPage" id="status_page">
<property name="icon-name">emblem-default-symbolic</property>
<property name="title">Done!</property>
<property name="description">Restart your PC to enjoy your Vanila OS experience.</property>
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<child>
<object class="GtkButton" id="btn_reboot">
<property name="label">Reboot Now</property>
<property name="halign">center</property>
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
</object>
</child>
<child>
<object class="GtkButton" id="btn_close">
<property name="label">Close</property>
<property name="halign">center</property>
<property name="visible">false</property>
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0" />
<template class="VanillaLayoutPreferences" parent="AdwBin">
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="AdwStatusPage" id="status_page">
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="vexpand">True</property>
<property name="hexpand">True</property>
<property name="valign">center</property>
<child>
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup" id="prefs_list"></object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="btn_next">
<property name="label">Next</property>
<property name="halign">center</property>
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0" />
<template class="VanillaLayoutYesNo" parent="AdwBin">
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="AdwStatusPage" id="status_page">
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="vexpand">True</property>
<property name="hexpand">True</property>
<property name="valign">center</property>
<child>
<object class="GtkBox">
<property name="valign">center</property>
<property name="spacing">10</property>
<property name="halign">center</property>
<child>
<object class="GtkButton" id="btn_no">
<property name="label">No</property>
<property name="halign">center</property>
<style>
<class name="pill" />
</style>
</object>
</child>
<child>
<object class="GtkButton" id="btn_yes">
<property name="label">Yes, enable it</property>
<property name="halign">center</property>
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
</object>
</child>
<child>
<object class="GtkButton" id="btn_info">
<property name="icon-name">dialog-information-symbolic</property>
<property name="visible">false</property>
<style>
<class name="flat" />
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0" />
<template class="VanillaProgress" parent="AdwBin">
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="AdwStatusPage" id="status_page">
<property name="title">Please Wait…</property>
<property name="description">The changes are being applied.</property>
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
<child>
<object class="GtkSpinner" id="spinner">
<property name="valign">center</property>
<property name="halign">center</property>
<property name="spinning">true</property>
</object>
</child>
</object>
</child>
</template>
</interface>

@ -2,7 +2,7 @@
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0" />
<template class="FirstSetupWindow" parent="AdwApplicationWindow">
<template class="VanillaWindow" parent="AdwApplicationWindow">
<property name="default-width">750</property>
<property name="default-height">640</property>
<property name="title">Vanilla OS First Setup</property>
@ -11,15 +11,22 @@
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar">
<style>
<class name="flat" />
</style>
<property name="title_widget">
<object class="AdwCarouselIndicatorDots">
<property name="carousel">carousel</property>
<property name="orientation">horizontal</property>
</object>
</property>
<style>
<class name="flat" />
</style>
<child type="start">
<object class="GtkButton" id="btn_back">
<property name="label">Back</property>
<property name="halign">center</property>
<property name="visible">False</property>
</object>
</child>
</object>
</child>
<child>
@ -66,7 +73,7 @@
<property name="halign">center</property>
<child>
<object class="GtkCheckButton" id="btn_light">
<property name="tooltip-text">Default</property>
<property name="tooltip-text">Light</property>
<property name="halign">center</property>
<property name="active">True</property>
<style>
@ -91,7 +98,8 @@
</child>
<child>
<object class="AdwStatusPage">
<property name="title">Style</property>
<property name="title">Light or Dark?</property>
<property name="description">Choose your preferred theme.</property>
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
@ -114,7 +122,7 @@
<child>
<object class="AdwStatusPage">
<property name="icon-name">vanilla-package-symbolic</property>
<property name="title">Package Manager</property>
<property name="title">Choose the Package Manager</property>
<property name="description">Choose one or more package managers from the following options.</property>
<property name="halign">fill</property>
<property name="valign">fill</property>
@ -208,7 +216,7 @@
<property name="halign">center</property>
<child>
<object class="GtkButton" id="btn_no_subsystem">
<property name="label">Skip</property>
<property name="label">No</property>
<property name="halign">center</property>
<style>
<class name="pill" />
@ -217,7 +225,7 @@
</child>
<child>
<object class="GtkButton" id="btn_use_subsystem">
<property name="label">Enable</property>
<property name="label">Yes, enable it</property>
<property name="halign">center</property>
<style>
<class name="pill" />
@ -244,8 +252,8 @@
<child>
<object class="AdwStatusPage" id="status_nvidia">
<property name="icon-name">video-display-symbolic</property>
<property name="title">NVIDIA Drivers</property>
<property name="description">Choose whether to install proprietary NVIDIA drivers for better compatibility and performance.</property>
<property name="title">Proprietary Nvidia Drivers</property>
<property name="description">Want to use Nvidia proprietary drivers for better performance and compatibility?</property>
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="hexpand">true</property>
@ -265,7 +273,7 @@
<property name="halign">center</property>
<child>
<object class="GtkButton" id="btn_no_prop_nvidia">
<property name="label">Skip</property>
<property name="label">No</property>
<property name="halign">center</property>
<style>
<class name="pill" />
@ -274,7 +282,7 @@
</child>
<child>
<object class="GtkButton" id="btn_use_prop_nvidia">
<property name="label">Install</property>
<property name="label">Yes, install</property>
<property name="halign">center</property>
<style>
<class name="pill" />

@ -0,0 +1,10 @@
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
layoutsdir = join_paths(pkgdatadir, 'vanilla_first_setup/layouts')
sources = [
'__init__.py',
'preferences.py',
'yes_no.py',
]
install_data(sources, install_dir: layoutsdir)

@ -0,0 +1,68 @@
# preferences.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
from gi.repository import Gtk, Gio, GLib, Adw
from vanilla_first_setup.utils.run_async import RunAsync
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/layout-preferences.ui')
class VanillaLayoutPreferences(Adw.Bin):
__gtype_name__ = 'VanillaLayoutPreferences'
status_page = Gtk.Template.Child()
prefs_list = Gtk.Template.Child()
btn_next = Gtk.Template.Child()
def __init__(self, window, distro_info, key, step, **kwargs):
super().__init__(**kwargs)
self.__window = window
self.__distro_info = distro_info
self.__key = key
self.__step = step
self.__register_widgets = []
self.__build_ui()
# signals
self.btn_next.connect("clicked", self.__window.next)
def __build_ui(self):
self.status_page.set_icon_name(self.__step["icon"])
self.status_page.set_title(self.__step["title"])
self.status_page.set_description(self.__step["description"])
for item in self.__step["preferences"]:
_action_row = Adw.ActionRow(
title=item["title"],
subtitle=item.get("subtitle", "")
)
_switcher = Gtk.Switch()
_switcher.set_active(item.get("default", False))
_switcher.set_valign(Gtk.Align.CENTER)
_action_row.add_suffix(_switcher)
self.prefs_list.add(_action_row)
self.__register_widgets.append((item["id"], _switcher))
def get_finals(self):
finals = {"vars": {}, "funcs": [x for x in self.__step["final"]]}
for _id, switcher in self.__register_widgets:
finals["vars"][_id] = switcher.get_active()
return finals

@ -0,0 +1,79 @@
# yes_no.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
from gi.repository import Gtk, Gio, GLib, Adw
from vanilla_first_setup.utils.run_async import RunAsync
from vanilla_first_setup.dialog import VanillaDialog
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/layout-yes-no.ui')
class VanillaLayoutYesNo(Adw.Bin):
__gtype_name__ = 'VanillaLayoutYesNo'
status_page = Gtk.Template.Child()
btn_no = Gtk.Template.Child()
btn_yes = Gtk.Template.Child()
btn_info = Gtk.Template.Child()
def __init__(self, window, distro_info, key, step, **kwargs):
super().__init__(**kwargs)
self.__window = window
self.__distro_info = distro_info
self.__key = key
self.__step = step
self.__response = False
self.__build_ui()
# signals
self.btn_yes.connect("clicked", self.__on_response, True)
self.btn_no.connect("clicked", self.__on_response, False)
self.btn_info.connect("clicked", self.__on_info)
def __build_ui(self):
self.status_page.set_icon_name(self.__step["icon"])
self.status_page.set_title(self.__step["title"])
self.status_page.set_description(self.__step["description"])
self.btn_yes.set_label(self.__step["buttons"]["yes"])
self.btn_no.set_label(self.__step["buttons"]["no"])
if "info" in self.__step["buttons"]:
self.btn_info.set_visible(True)
def __on_response(self, _, response):
self.__response = response
self.__window.next()
def __on_info(self, _):
if "info" not in self.__step["buttons"]:
return
dialog = VanillaDialog(
self.__window,
self.__step["buttons"]["info"]["title"],
self.__step["buttons"]["info"]["text"]
)
dialog.show()
def get_finals(self):
return {
"vars": {
self.__key: self.__response
},
"funcs": [x for x in self.__step["final"]]
}

@ -23,7 +23,7 @@ gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Gdk, Gio, Adw
from vanilla_first_setup.window import FirstSetupWindow
from vanilla_first_setup.window import VanillaWindow
logging.basicConfig(level=logging.INFO)
@ -99,7 +99,7 @@ class FirstSetupApplication(Adw.Application):
win = self.props.active_window
if not win:
win = FirstSetupWindow(application=self)
win = VanillaWindow(application=self)
win.present()
def create_action(self, name, callback, shortcuts=None):

@ -34,13 +34,15 @@ configure_file(
)
subdir('utils')
subdir('models')
subdir('dialogs')
subdir('defaults')
subdir('layouts')
subdir('views')
vanilla_first_setup_sources = [
'__init__.py',
'main.py',
'window.py',
'dialog.py',
]
install_data(vanilla_first_setup_sources, install_dir: moduledir)

@ -1,73 +0,0 @@
import logging
logger = logging.getLogger("FirstSetup::Config")
class Config:
def __init__(
self,
snap: bool,
flatpak: bool,
appimage: bool,
apport: bool,
apx: bool,
nvidia: bool,
):
self.snap = snap
self.flatpak = flatpak
self.appimage = appimage
self.apport = apport
self.apx = apx
self.nvidia = nvidia
def get_str(self) -> str:
keys = [
"snap", "flatpak", "appimage", "apport", "apx", "nvidia"
]
vals = [
self.snap, self.flatpak, self.appimage, self.apport, self.apx, self.nvidia
]
return "|".join([f"{key}::{val}" for key, val in zip(keys, vals)])
def set_val(self, key: str, val: bool):
if key == "snap":
self.snap = val
elif key == "flatpak":
self.flatpak = val
elif key == "appimage":
self.appimage = val
elif key == "apport":
self.apport = val
elif key == "apx":
self.apx = val
elif key == "nvidia":
self.nvidia = val
else:
return
logger.info(f"Setting {key} to {val}")
@classmethod
def from_str(cls, config_str: str) -> 'Config':
def get_bool(value: str):
return "True" in value
items = config_str.split('|')
snap = items[0].split('::')[1]
flatpak = items[1].split('::')[1]
appimage = items[2].split('::')[1]
apport = items[3].split('::')[1]
apx = items[4].split('::')[1]
nvidia = items[5].split('::')[1]
return cls(
snap=get_bool(snap),
flatpak=get_bool(flatpak),
appimage=get_bool(appimage),
apport=get_bool(apport),
apx=get_bool(apx),
nvidia=get_bool(nvidia),
)

@ -1,10 +0,0 @@
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
modelsdir = join_paths(pkgdatadir, 'vanilla_first_setup/models')
sources = [
'__init__.py',
'config.py',
'preset.py',
]
install_data(sources, install_dir: modelsdir)

@ -1,8 +0,0 @@
class Preset:
snap: bool = False
flatpak: bool = True
appimage: bool = False
apport: bool = False
apx: bool = True
nvidia: bool = False

@ -1,58 +0,0 @@
import os
import subprocess
class Apt:
env = os.environ.copy()
env['DEBIAN_FRONTEND'] = 'noninteractive'
@staticmethod
def install(packages: list):
subprocess.run(
['sudo', 'apt', 'install'] + packages + ['-y'],
env=Apt.env,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
@staticmethod
def remove(packages: list):
subprocess.run(
['sudo', 'apt', 'remove'] + packages + ['-y'],
env=Apt.env,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
@staticmethod
def purge(packages: list):
subprocess.run(
['sudo', 'apt', 'purge'] + packages + ['-y'],
env=Apt.env,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
@staticmethod
def update():
subprocess.run(
['sudo', 'apt', 'update'],
env=Apt.env,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
@staticmethod
def upgrade():
subprocess.run(
['sudo', 'apt', 'upgrade', '-y'],
env=Apt.env,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)

@ -0,0 +1,95 @@
# builder.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import logging
import json
from gi.repository import Gio
from vanilla_first_setup.utils.recipe import RecipeLoader
from vanilla_first_setup.defaults.welcome import VanillaDefaultWelcome
from vanilla_first_setup.defaults.theme import VanillaDefaultTheme
from vanilla_first_setup.layouts.preferences import VanillaLayoutPreferences
from vanilla_first_setup.layouts.yes_no import VanillaLayoutYesNo
logger = logging.getLogger("FirstSetup::Builder")
templates = {
"welcome": VanillaDefaultWelcome,
"theme": VanillaDefaultTheme,
"preferences": VanillaLayoutPreferences,
"yes-no": VanillaLayoutYesNo
}
class Builder:
def __init__(self, window):
self.__window = window
self.__recipe = RecipeLoader()
self.__register_widgets = []
self.__register_finals = []
self.__load()
def __load(self):
# here we create a temporary file to store the output of the commands
# the log path is defined in the recipe
if "log_file" not in self.__recipe.raw:
logger.critical("Missing 'log_file' in the recipe.")
sys.exit(1)
log_path = self.__recipe.raw["log_file"]
if not os.path.exists(log_path):
try:
open(log_path, 'a').close()
except OSError:
logger.critical("failed to create log file: %s" % log_path)
sys.exit(1)
for key, step in self.__recipe.raw["steps"].items():
if step["template"] in templates:
_widget = templates[step["template"]](self.__window, self.distro_info, key, step)
self.__register_widgets.append(_widget)
def get_finals(self):
self.__register_finals = []
for widget in self.__register_widgets:
self.__register_finals.append(widget.get_finals())
return self.__register_finals
@property
def widgets(self):
return self.__register_widgets
@property
def recipe(self):
return self.__recipe.raw
@property
def distro_info(self):
return {
"name": self.__recipe.raw["distro_name"],
"logo": self.__recipe.raw["distro_logo"]
}

@ -1,18 +0,0 @@
import subprocess
import shutil
def is_snap_installed():
return shutil.which('snap') is not None
def is_flatpak_installed():
return shutil.which('flatpak') is not None
def is_apport_installed():
return shutil.which('apport') is not None
def has_nvidia_gpu():
return subprocess.run(['lspci'], stdout=subprocess.PIPE).stdout.decode('utf-8').find('VGA compatible controller: NVIDIA Corporation') != -1

@ -1,128 +0,0 @@
import os
import time
import logging
import subprocess
from gi.repository import Gio
from vanilla_first_setup.utils import checks
from vanilla_first_setup.utils.apt import Apt
from vanilla_first_setup.utils.flatpak import Flatpak
from vanilla_first_setup.utils.snap import Snap
logger = logging.getLogger("FirstSetup::Configurator")
class Configurator:
def __init__(self, config: 'Config', fake: bool = False):
self.config = config
self.fake = fake
def apply(self):
logging.info(f"Applying config: {self.config.get_str()}")
self.__enable_snap() if self.config.snap else self.__disable_snap()
self.__enable_flatpak() if self.config.flatpak else self.__disable_flatpak()
self.__enable_appimage() if self.config.appimage else self.__disable_appimage()
self.__enable_apport() if self.config.apport else self.__disable_apport()
if self.config.apx:
self.__enable_apx()
if self.config.nvidia:
self.__enable_nvidia()
def __fake(self, msg: str):
time.sleep(1)
logger.info(f"Fake: {msg}")
def __enable_snap(self):
if self.fake:
return self.__fake("Fake: Snap enabled")
if not checks.is_snap_installed():
Apt.install(['snapd', 'gnome-software-plugin-snap'])
Apt.update()
if not self.config.flatpak:
Snap.install(['snap-store'])
def __disable_snap(self):
if self.fake:
return self.__fake("Fake: Snap disabled")
if checks.is_snap_installed():
Apt.purge(['snapd'])
def __enable_flatpak(self):
if self.fake:
return self.__fake("Fake: Flatpak enabled")
if not checks.is_flatpak_installed():
Apt.install(['flatpak'])
Apt.update()
Flatpak.add_repo("https://flathub.org/repo/flathub.flatpakrepo")
def __disable_flatpak(self):
if self.fake:
return self.__fake("Fake: Flatpak disabled")
if checks.is_flatpak_installed():
Apt.purge(['flatpak'])
def __enable_appimage(self):
if self.fake:
return self.__fake("Fake: AppImage enabled")
Apt.install(['fuse2'])
Apt.update()
def __disable_appimage(self):
if self.fake:
return self.__fake("Fake: AppImage disabled")
# Apt.purge(['libfuse2']) # NOTE: we should not remove libfuse2, it may be needed by other packages at this point
def __enable_apport(self):
if self.fake:
return self.__fake("Fake: Apport enabled")
if not checks.is_apport_installed():
Apt.install(['apport'])
Apt.update()
subprocess.run(['sudo', 'systemctl', 'start', 'apport.service'])
def __disable_apport(self):
if self.fake:
return self.__fake("Fake: Apport disabled")
if checks.is_apport_installed():
subprocess.run(['sudo', 'systemctl', 'stop', 'apport.service'])
Apt.purge(['apport'])
def __enable_apx(self):
if self.fake:
return self.__fake("Fake: apx enabled")
Apt.install(['curl', 'podman', 'apx'])
Apt.update()
proc = subprocess.run(['curl', '-s', 'https://raw.githubusercontent.com/89luca89/distrobox/main/install'], stdout=subprocess.PIPE)
proc = subprocess.run(['sudo', 'sh'], input=proc.stdout, stdout=subprocess.PIPE)
def __enable_nvidia(self):
if self.fake:
return self.__fake("Fake: Nvidia enabled")
subprocess.run(['sudo', 'ubuntu-drivers', 'install', '--recommended'])
def __disable_on_startup(self):
if self.fake:
return self.__fake("Fake: Disable on startup")
autostart_file = os.path.expanduser("~/.config/autostart/io.github.vanilla-os.FirstSetup.desktop")
if os.path.exists(autostart_file):
os.remove(autostart_file)
@staticmethod
def reboot():
subprocess.run(['gnome-session-quit', '--reboot'])

@ -1,38 +0,0 @@
import os
import subprocess
class Flatpak:
env = os.environ.copy()
@staticmethod
def install(packages: list):
proc = subprocess.Popen(
['flatpak', 'install', '--user'] + packages,
env=Flatpak.env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
proc.communicate(input=b'y\n')
@staticmethod
def remove(packages: list):
proc = subprocess.Popen(
['flatpak', 'remove', '--user'] + packages,
env=Flatpak.env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
proc.communicate(input=b'y\n')
@staticmethod
def add_repo(repo: str):
proc = subprocess.Popen(
['flatpak', 'remote-add', 'test', '--user', '--if-not-exists', repo],
env=Flatpak.env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
proc.communicate(input=b'y\n')

@ -3,14 +3,11 @@ utilsdir = join_paths(pkgdatadir, 'vanilla_first_setup/utils')
sources = [
'__init__.py',
'checks.py',
'apt.py',
'flatpak.py',
'snap.py',
'configurator.py',
'run_async.py',
'processor.py',
'welcome_langs.py',
'recipe.py',
'builder.py',
'parser.py',
]
install_data(sources, install_dir: utilsdir)

@ -0,0 +1,62 @@
# parser.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import logging
import json
logger = logging.getLogger("FirstSetup::Parser")
class Parser:
supported_types = ["command"]
@staticmethod
def parse(finals):
commands = []
for final in finals:
if len(final) == 0:
continue
_vars = final["vars"]
for _func in final["funcs"]:
if "if" not in _func:
logger.critical(f"Missing an 'if' operand in {_func}")
sys.exit(1)
if _func["if"] not in _vars:
logger.critical(
f"Missing a variable named '{_func['if']}' in the 'vars' section.")
sys.exit(1)
if _func.get("type") not in Parser.supported_types:
logger.critical(
f"Unsupported final type: {_func.get('type')}")
sys.exit(1)
# assume True if no condition is given
_condition = _func.get("condition", True)
# check if the condition is met
if _condition == _vars[_func["if"]]:
commands += _func["commands"]
return commands

@ -1,4 +1,22 @@
# processor.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import tempfile
import subprocess
@ -7,18 +25,50 @@ logger = logging.getLogger("FirstSetup::Processor")
class Processor:
def __init__(self, config: 'Config'):
self.__config = config
@staticmethod
def run(log_path, commands):
logger.info("processing the following commands: \n%s" %
'\n'.join(commands))
# generating a temporary file to store all the commands so we can
# run them all at once
with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
f.write("#!/bin/sh\n")
f.write("# This file was created by FirstSetup\n")
f.write("# Do not edit this file manually\n\n")
for command in commands:
f.write(f"{command}\n")
f.flush()
f.close()
# setting the file executable
os.chmod(f.name, 0o755)
# here we run the file trough pkexec to get root privileges
# log the output to the log file
proc = subprocess.run(
["pkexec", "sh", f.name],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
# write the output to the log file so the packager can see what
# happened during the installation process
with open(log_path, 'a') as log:
log.write(proc.stdout.decode('utf-8'))
log.flush()
def run(self):
logger.info(f"Spawning processor with config: {self.__config.get_str()}")
if proc.returncode != 0:
logger.critical(
"Error while processing commands, see log for details.")
return False
proc = subprocess.run(
["pkexec", "vanilla-first-setup-processor", self.__config.get_str()],
check=True
)
if proc.returncode != 0:
return False, "Error executing the Vanilla OS First Setup Processor"
autostart_file = os.path.expanduser(
"~/.config/autostart/io.github.vanilla-os.FirstSetup.desktop")
if os.path.exists(autostart_file):
os.remove(autostart_file)
return True
return True

@ -0,0 +1,74 @@
# recipe.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import logging
import json
from gi.repository import Gio
logger = logging.getLogger("FirstSetup::RecipeLoader")
class RecipeLoader:
recipe_path = "/etc/vanilla-first-setup/recipe.json"
def __init__(self):
self.__recipe = {}
self.__load()
def __load(self):
if "VANILLA_CUSTOM_RECIPE" in os.environ:
self.recipe_path = os.environ["VANILLA_CUSTOM_RECIPE"]
if os.path.exists(self.recipe_path):
with open(self.recipe_path, "r") as f:
self.__recipe = json.load(f)
return
if not self.__validate():
logger.error("Invalid recipe file")
sys.exit(1)
logger.error(f"Recipe not found at {self.recipe_path}")
sys.exit(1)
def __validate(self):
essential_keys = ["log_file", "distro_name", "distro_logo", "steps"]
if not isinstance(self.__recipe, dict):
logger.error("Recipe is not a dictionary")
return False
for key in essential_keys:
if key not in self.__recipe:
logger.error(f"Recipe is missing the '{key}' key")
return False
if not isinstance(self.__recipe["steps"], list):
logger.error("Recipe steps is not a list")
return False
for step in self.__recipe["steps"]:
if not isinstance(step, dict):
logger.error(f"Step {step} is not a dictionary")
return False
@property
def raw(self):
return self.__recipe

@ -40,7 +40,8 @@ class RunAsync(threading.Thread):
self.source_id = None
assert threading.current_thread() is threading.main_thread()
super(RunAsync, self).__init__(target=self.__target, args=args, kwargs=kwargs)
super(RunAsync, self).__init__(
target=self.__target, args=args, kwargs=kwargs)
self.task_func = task_func
@ -59,7 +60,7 @@ class RunAsync(threading.Thread):
result = self.task_func(*args, **kwargs)
except Exception as exception:
logger.error("Error while running async job: "
f"{self.task_func}\nException: {exception}")
f"{self.task_func}\nException: {exception}")
error = exception
_ex_type, _ex_value, trace = sys.exc_info()

@ -1,26 +0,0 @@
import os
import subprocess
class Snap:
env = os.environ.copy()
@staticmethod
def install(packages: list):
subprocess.run(
['sudo', 'snap', 'install'] + packages,
env=Snap.env,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
@staticmethod
def remove(packages: list):
subprocess.run(
['sudo', 'snap', 'remove'] + packages,
env=Snap.env,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)

@ -1,36 +0,0 @@
welcome = [
'Welcome',
'Benvenuto',
'Bienvenido',
'Bienvenue',
'Willkommen',
'Bem-vindo',
'Добро пожаловать',
'欢迎',
'ようこそ',
'환영합니다',
'أهلا بك',
'ברוך הבא',
'Καλώς ήρθατε',
'Hoşgeldiniz',
'Welkom',
'Witamy',
'Välkommen',
'Tervetuloa',
'Vítejte',
'Üdvözöljük',
'Bun venit',
'Vitajte',
'Tere tulemast',
'Sveiki atvykę',
'Dobrodošli',
'خوش آمدید',
'आपका स्वागत है',
'স্বাগতম',
'வரவேற்கிறோம்',
'స్వాగతం',
'मुबारक हो',
'સુસ્વાગત છે',
'ಸುಸ್ವಾಗತ',
'സ്വാഗതം'
]

@ -2,8 +2,15 @@
<gresources>
<gresource prefix="/io/github/vanilla-os/FirstSetup">
<file>gtk/window.ui</file>
<file>gtk/dialog-subsystem.ui</file>
<file>gtk/dialog-prop-nvidia.ui</file>
<file>gtk/done.ui</file>
<file>gtk/progress.ui</file>
<file>gtk/dialog.ui</file>
<file>gtk/default-theme.ui</file>
<file>gtk/default-welcome.ui</file>
<file>gtk/layout-preferences.ui</file>
<file>gtk/layout-yes-no.ui</file>
</gresource>
<gresource prefix="/io/github/vanilla-os/FirstSetup/icons/scalable/actions/">
<file preprocess="xml-stripblanks">../data/icons/hicolor/symbolic/actions/vanilla-package-symbolic.svg</file>

@ -0,0 +1,54 @@
# done.py
#
# Copyright 2022 mirkobrombin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundationat version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import subprocess
from gi.repository import Gtk, Adw
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/done.ui')
class VanillaDone(Adw.Bin):
__gtype_name__ = 'VanillaDone'
status_page = Gtk.Template.Child()
btn_reboot = Gtk.Template.Child()
btn_close = Gtk.Template.Child()
def __init__(self, window, **kwargs):
super().__init__(**kwargs)
self.__window = window
self.status_page.set_description(
"Restart your PC to enjoy your {} experience.".format(
self.__window.recipe["distro_name"]
)
)
self.btn_reboot.connect("clicked", self.__on_reboot_clicked)
self.btn_close.connect("clicked", self.__on_close_clicked)
def set_result(self, result):
if not result:
self.status_page.set_icon_name("dialog-error-symbolic")
self.status_page.set_title("Something went wrong")
self.status_page.set_description("Please contact the distribution developers.")
self.btn_reboot.set_visible(False)
self.btn_close.set_visible(True)
def __on_reboot_clicked(self, button):
subprocess.run(['gnome-session-quit', '--reboot'])
def __on_close_clicked(self, button):
self.__window.close()

@ -0,0 +1,10 @@
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
viewsdir = join_paths(pkgdatadir, 'vanilla_first_setup/views')
sources = [
'__init__.py',
'done.py',
'progress.py',
]
install_data(sources, install_dir: viewsdir)

@ -1,4 +1,4 @@
# subsystem.py
# progress.py
#
# Copyright 2022 mirkobrombin
#
@ -17,10 +17,10 @@
from gi.repository import Gtk, Adw
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/dialog-subsystem.ui')
class SubSystemDialog(Adw.Window):
__gtype_name__ = 'SubSystemDialog'
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/progress.ui')
class VanillaProgress(Adw.Bin):
__gtype_name__ = 'VanillaProgress'
def __init__(self, window, **kwargs):
super().__init__(**kwargs)
self.set_transient_for(window)
self.__window = window

@ -17,171 +17,88 @@
import time
from gi.repository import Gtk, Gio, GLib, Adw
from vanilla_first_setup.models.preset import Preset
from vanilla_first_setup.models.config import Config
from vanilla_first_setup.utils.builder import Builder
from vanilla_first_setup.utils.parser import Parser
from vanilla_first_setup.utils.processor import Processor
from vanilla_first_setup.utils.run_async import RunAsync
from vanilla_first_setup.utils.configurator import Configurator
from vanilla_first_setup.utils.welcome_langs import welcome
from vanilla_first_setup.utils.checks import has_nvidia_gpu
from vanilla_first_setup.dialogs.subsystem import SubSystemDialog
from vanilla_first_setup.dialogs.prop_drivers import ProprietaryDriverDialog
from vanilla_first_setup.views.progress import VanillaProgress
from vanilla_first_setup.views.done import VanillaDone
@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/window.ui')
class FirstSetupWindow(Adw.ApplicationWindow):
__gtype_name__ = 'FirstSetupWindow'
class VanillaWindow(Adw.ApplicationWindow):
__gtype_name__ = 'VanillaWindow'
carousel = Gtk.Template.Child()
btn_go_theme = Gtk.Template.Child()
btn_light = Gtk.Template.Child()
btn_dark = Gtk.Template.Child()
btn_go_package = Gtk.Template.Child()
btn_go_subsystem = Gtk.Template.Child()
btn_save = Gtk.Template.Child()
btn_reboot = Gtk.Template.Child()
btn_no_subsystem = Gtk.Template.Child()
btn_use_subsystem = Gtk.Template.Child()
btn_info_subsystem = Gtk.Template.Child()
btn_no_prop_nvidia = Gtk.Template.Child()
btn_use_prop_nvidia = Gtk.Template.Child()
btn_info_prop_nvidia = Gtk.Template.Child()
switch_snap = Gtk.Template.Child()
switch_flatpak = Gtk.Template.Child()
switch_appimage = Gtk.Template.Child()
switch_apport = Gtk.Template.Child()
spinner = Gtk.Template.Child()
status_welcome = Gtk.Template.Child()
status_nvidia = Gtk.Template.Child()
pages = ["welcome", "theme", "package", "subsystem", "nvidia", "extras", "progress", "done"]
btn_back = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__config = Config(
snap=Preset.snap,
flatpak=Preset.flatpak,
appimage=Preset.appimage,
apport=Preset.apport,
apx=Preset.apx,
nvidia=Preset.nvidia,
)
self.__has_nvidia = has_nvidia_gpu()
self.__buiild_ui()
self.__connect_signals()
self.__start_welcome_animation()
# this starts the builder and generates the widgets
# to put in the carousel
self.__builder = Builder(self)
self.recipe = self.__builder.recipe
def __buiild_ui(self):
if self.__has_nvidia:
self.status_nvidia.set_visible(True)
else:
self.pages.remove('nvidia')
# system views
self.__view_progress = VanillaProgress(self)
self.__view_done = VanillaDone(self)
self.switch_snap.set_active(Preset.snap)
self.switch_flatpak.set_active(Preset.flatpak)
self.switch_appimage.set_active(Preset.appimage)
self.switch_apport.set_active(Preset.apport)
# this builds the UI with the widgets generated by the builder
self.__build_ui()
self.btn_dark.set_group(self.btn_light)
# connect system signals
self.__connect_signals()
def __connect_signals(self):
# go to pages
self.btn_go_theme.connect('clicked', self.__show_page, 'theme')
self.btn_go_package.connect('clicked', self.__show_page, 'package')
self.btn_go_subsystem.connect('clicked', self.__show_page, 'subsystem')
# save
self.btn_save.connect('clicked', self.__on_btn_save_clicked)
# reboot
self.btn_reboot.connect('clicked', self.__on_btn_reboot_clicked)
# theme
self.btn_light.connect('toggled', self.__set_theme, "light")
self.btn_dark.connect('toggled', self.__set_theme, "dark")
# subsystem
self.btn_no_subsystem.connect('clicked', self.__on_btn_subsystem_clicked, False)
self.btn_use_subsystem.connect('clicked', self.__on_btn_subsystem_clicked, True)
self.btn_info_subsystem.connect('clicked', self.__on_btn_info_subsystem_clicked)
# nvidia
self.btn_no_prop_nvidia.connect('clicked', self.__on_btn_prop_nvidia_clicked, False)
self.btn_use_prop_nvidia.connect('clicked', self.__on_btn_prop_nvidia_clicked, True)
self.btn_info_prop_nvidia.connect('clicked', self.__on_btn_info_prop_nvidia_clicked)
# snap
self.switch_snap.connect('state-set', self.__on_switch_snap_state_set)
# flatpak
self.switch_flatpak.connect('state-set', self.__on_switch_flatpak_state_set)
# appimage
self.switch_appimage.connect('state-set', self.__on_switch_appimage_state_set)
# apport
self.switch_apport.connect('state-set', self.__on_switch_apport_state_set)
def __on_btn_save_clicked(self, widget):
def __on_done(result, error=None):
self.spinner.stop()
self.__show_page(page='done')
self.__show_page(page='progress')
self.spinner.start()
RunAsync(Processor(self.__config).run, __on_done)
def __set_theme(self, widget, theme: str):
self.__config.set_val('theme', theme)
pref = "prefer-dark" if theme == "dark" else "default"
gtk = "Adwaita-dark" if theme == "dark" else "Adwaita"
Gio.Settings.new("org.gnome.desktop.interface").set_string("color-scheme", pref)
Gio.Settings.new("org.gnome.desktop.interface").set_string("gtk-theme", gtk)
def __on_switch_snap_state_set(self, widget, state):
self.__config.set_val('snap', state)
def __on_switch_flatpak_state_set(self, widget, state):
self.__config.set_val('flatpak', state)
def __on_switch_appimage_state_set(self, widget, state):
self.__config.set_val('appimage', state)
def __on_switch_apport_state_set(self, widget, state):
self.__config.set_val('apport', state)
def __on_switch_apx_state_set(self, widget, state):
self.__config.set_val('apx', state)
def __on_btn_reboot_clicked(self, widget):
Configurator.reboot()
def __on_btn_subsystem_clicked(self, widget, state):
self.__config.set_val('apx', state)
self.__show_page(page='nvidia' if self.__has_nvidia else 'extras')
def __on_btn_info_subsystem_clicked(self, widget):
SubSystemDialog(self).show()
def __on_btn_prop_nvidia_clicked(self, widget, state):
self.__config.set_val('nvidia', state)
self.__show_page(page='extras')
def __on_btn_info_prop_nvidia_clicked(self, widget):
ProprietaryDriverDialog(self).show()
def __start_welcome_animation(self):
def change_langs():
for lang in welcome:
GLib.idle_add(self.status_welcome.set_title, lang )
time.sleep(1.5)
RunAsync(change_langs, None)
def __get_page(self, page: str):
return self.pages.index(page)
def __show_page(self, widget=None, page: str='welcome'):
_page = self.carousel.get_nth_page(self.__get_page(page))
self.carousel.scroll_to(_page, True)
self.btn_back.connect("clicked", self.back)
self.carousel.connect("page-changed", self.__on_page_changed)
def __build_ui(self):
for widget in self.__builder.widgets:
self.carousel.append(widget)
self.carousel.append(self.__view_progress)
self.carousel.append(self.__view_done)
def __on_page_changed(self, *args):
def process():
# this parses the finals to compatible commands, by replacing the
# placeholders with the actual values and generating shell commands
commands = Parser.parse(finals)
# process the commands
return Processor.run(self.recipe["log_file"], commands)
def on_done(result, *args):
self.__view_done.set_result(result)
self.next()
cur_index = self.carousel.get_position()
page = self.carousel.get_nth_page(cur_index)
if page not in [self.__view_progress, self.__view_done]:
self.btn_back.set_visible(cur_index != 0.0)
return
self.btn_back.set_visible(False)
# keep the btn_back button locked if this is the last page
if page == self.__view_done:
return
# collect all the finals
finals = self.__builder.get_finals()
# run the process in a thread
RunAsync(process, on_done)
def next(self, *args):
cur_index = self.carousel.get_position()
page = self.carousel.get_nth_page(cur_index + 1)
self.carousel.scroll_to(page, True)
def back(self, *args):
cur_index = self.carousel.get_position()
page = self.carousel.get_nth_page(cur_index - 1)
self.carousel.scroll_to(page, True)

Loading…
Cancel
Save