diff --git a/recipe.json b/recipe.json index 3ab7941..53df8c0 100644 --- a/recipe.json +++ b/recipe.json @@ -50,6 +50,9 @@ "theme": { "template": "theme" }, + "user": { + "template": "user" + }, "packages": { "template": "preferences", "is-advanced": true, diff --git a/vanilla_first_setup/defaults/applications.py b/vanilla_first_setup/defaults/applications.py index 3572ebd..ff71f98 100644 --- a/vanilla_first_setup/defaults/applications.py +++ b/vanilla_first_setup/defaults/applications.py @@ -39,7 +39,7 @@ class VanillaLayoutApplications(Adw.Bin): # signals self.btn_next.connect("clicked", self.__next_step) self.__window.connect("page-changed", self.__on_page_changed) - + @property def step_id(self): return self.__key @@ -92,7 +92,7 @@ class VanillaLayoutApplications(Adw.Bin): def close_customize(widget, dialog): dialog.hide() - + def apply_preferences(widget, dialog, apps_list, item): for app in item["applications"]: app["active"] = app["switch"].get_active() @@ -148,12 +148,12 @@ class VanillaLayoutApplications(Adw.Bin): _customize.connect("clicked", present_customize, selection_dialogs[-1], _apps_list, item) _cancel_button.connect("clicked", close_customize, selection_dialogs[-1]) _apply_button.connect("clicked", apply_preferences, selection_dialogs[-1], _apps_list, item) - + self.bundles_list.add(_action_row) self.__register_widgets.append((item["id"], _switcher, _index)) _index += 1 - + def __on_page_changed(self, widget, page): if page == self.__key: if True not in [ @@ -163,7 +163,7 @@ class VanillaLayoutApplications(Adw.Bin): self.bundles_list.set_sensitive(False) else: self.bundles_list.set_sensitive(True) - + def __next_step(self, *args): self.__window.next() @@ -192,4 +192,4 @@ class VanillaLayoutApplications(Adw.Bin): for app in self.__step["bundles"][index]["applications"]: finals["vars"][app["name"]] = False - return finals \ No newline at end of file + return finals diff --git a/vanilla_first_setup/defaults/meson.build b/vanilla_first_setup/defaults/meson.build index 32cf3c9..01a406f 100644 --- a/vanilla_first_setup/defaults/meson.build +++ b/vanilla_first_setup/defaults/meson.build @@ -5,6 +5,7 @@ sources = [ '__init__.py', 'welcome.py', 'theme.py', + 'user.py', 'applications.py', 'conn_check.py' ] diff --git a/vanilla_first_setup/defaults/user.py b/vanilla_first_setup/defaults/user.py new file mode 100644 index 0000000..080a769 --- /dev/null +++ b/vanilla_first_setup/defaults/user.py @@ -0,0 +1,150 @@ +# user.py +# +# Copyright 2022 mirkobrombin +# Copyright 2022 muqtadir +# +# +# 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 . + +import sys +import time +import re, subprocess, shutil +from gi.repository import Gtk, Gio, GLib, Adw + + +@Gtk.Template(resource_path='/io/github/vanilla-os/FirstSetup/gtk/default-user.ui') +class VanillaDefaultUser(Adw.Bin): + __gtype_name__ = 'VanillaDefaultUser' + + btn_next = Gtk.Template.Child() + fullname_entry = Gtk.Template.Child() + username_entry = Gtk.Template.Child() + password_entry = Gtk.Template.Child() + password_confirmation = Gtk.Template.Child() + + fullname = "" + fullname_filled = False + username = "" + username_filled = False + password_filled = False + + 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 + + # signals + self.btn_next.connect("clicked", self.__on_btn_next_clicked) + self.fullname_entry.connect('changed', self.__on_fullname_entry_changed) + self.username_entry.connect('changed', self.__on_username_entry_changed) + self.password_entry.connect('changed', self.__on_password_changed) + self.password_confirmation.connect('changed', self.__on_password_changed) + + @property + def step_id(self): + return self.__key + + def __on_btn_next_clicked(self, widget): + self.__window.set_user(self.username) + self.__window.next() + + def get_finals(self): + return { + "vars": { + "create": True + }, + "funcs": [ + { + "if": "create", + "type": "command", + "commands": [ + f"adduser --quiet --disabled-password --shell /bin/bash --gecos \"{self.fullname}\" {self.username}", + f"echo \"{self.username}:{self.password_entry.get_text()}\" | chpasswd" + ] + } + ] + } + + def __on_fullname_entry_changed(self, *args): + _fullname = self.fullname_entry.get_text() + + if len(_fullname) > 32: + self.fullname_entry.set_text(_fullname[:32]) + self.fullname_entry.set_position(-1) + _fullname = self.fullname_entry.get_text() + + self.fullname_filled = True + self.__verify_continue() + self.fullname = _fullname + + def __on_username_entry_changed(self, *args): + _input = self.username_entry.get_text() + _status = True + + # cannot be longer than 32 characters + if len(_input) > 32: + self.username_entry.set_text(_input[:32]) + self.username_entry.set_position(-1) + _input = self.username_entry.get_text() + + # cannot contain special characters + if re.search(r'[^a-z0-9]', _input): + _status = False + self.__window.toast("Username cannot contain special characters or uppercase letters. Please choose another username.") + + # cannot be empty + elif not _input: + _status = False + self.__window.toast("Username cannot be empty. Please type a username.") + + # cannot be root + elif _input == "root": + _status = False + self.__window.toast("root user is reserved. Please choose another username.") + + if not _status: + self.username_entry.add_css_class('error') + self.username_filled = False + self.__verify_continue() + else: + self.username_entry.remove_css_class('error') + self.username_filled = True + self.__verify_continue() + self.username = _input + + + def __on_password_changed(self, *args): + password = self.password_entry.get_text() + if password == self.password_confirmation.get_text() \ + and password.strip(): + self.password_filled = True; + self.password_confirmation.remove_css_class('error') + self.password = self.__encrypt_password(password) + else: + self.password_filled = False; + self.password_confirmation.add_css_class('error') + + self.__verify_continue(); + + def __verify_continue(self): + self.btn_next.set_sensitive(self.fullname_filled and self.password_filled and self.username_filled) + + def __encrypt_password(self, password): + command = subprocess.run( + [shutil.which("openssl"), "passwd", "-crypt", password], + capture_output=True + ) + password_encrypted = command.stdout.decode('utf-8').strip('\n') + return password_encrypted diff --git a/vanilla_first_setup/gtk/default-user.ui b/vanilla_first_setup/gtk/default-user.ui new file mode 100644 index 0000000..6315266 --- /dev/null +++ b/vanilla_first_setup/gtk/default-user.ui @@ -0,0 +1,70 @@ + + + + + diff --git a/vanilla_first_setup/gtk/window.ui b/vanilla_first_setup/gtk/window.ui index 9fb70ff..18124b3 100644 --- a/vanilla_first_setup/gtk/window.ui +++ b/vanilla_first_setup/gtk/window.ui @@ -3,8 +3,8 @@ - \ No newline at end of file + diff --git a/vanilla_first_setup/meson.build b/vanilla_first_setup/meson.build index 59b0ee4..bd02177 100644 --- a/vanilla_first_setup/meson.build +++ b/vanilla_first_setup/meson.build @@ -20,6 +20,7 @@ conf.set('pkgdatadir', pkgdatadir) configure_file( input: 'vanilla-first-setup.in', output: 'vanilla-first-setup', + install_mode: 'rwxr-xr-x', configuration: conf, install: true, install_dir: get_option('bindir') diff --git a/vanilla_first_setup/utils/builder.py b/vanilla_first_setup/utils/builder.py index 894e673..e02d932 100644 --- a/vanilla_first_setup/utils/builder.py +++ b/vanilla_first_setup/utils/builder.py @@ -24,6 +24,7 @@ from vanilla_first_setup.utils.recipe import RecipeLoader from vanilla_first_setup.defaults.conn_check import VanillaDefaultConnCheck from vanilla_first_setup.defaults.welcome import VanillaDefaultWelcome from vanilla_first_setup.defaults.theme import VanillaDefaultTheme +from vanilla_first_setup.defaults.user import VanillaDefaultUser from vanilla_first_setup.layouts.preferences import VanillaLayoutPreferences from vanilla_first_setup.layouts.yes_no import VanillaLayoutYesNo @@ -37,6 +38,7 @@ templates = { "conn-check": VanillaDefaultConnCheck, "welcome": VanillaDefaultWelcome, "theme": VanillaDefaultTheme, + "user": VanillaDefaultUser, "preferences": VanillaLayoutPreferences, "yes-no": VanillaLayoutYesNo, "applications": VanillaLayoutApplications @@ -102,7 +104,7 @@ class Builder: if step["template"] in templates: _widget = templates[step["template"]](self.__window, self.distro_info, key, step) self.__register_widgets.append((_widget, _status, _protected)) - + def get_temp_finals(self, step_id: str): for widget, _, _ in self.__register_widgets: if widget.step_id == step_id: @@ -125,7 +127,7 @@ class Builder: @property def recipe(self): return self.__recipe.raw - + @property def distro_info(self): return { diff --git a/vanilla_first_setup/utils/parser.py b/vanilla_first_setup/utils/parser.py index 7855b32..b01473d 100644 --- a/vanilla_first_setup/utils/parser.py +++ b/vanilla_first_setup/utils/parser.py @@ -74,7 +74,7 @@ class Parser: # check if the condition is met if _condition == _vars[_func["if"]]: commands += _func["commands"] - + # set-up warps if any for warp in warps: _vars = warp["vars"] diff --git a/vanilla_first_setup/utils/processor.py b/vanilla_first_setup/utils/processor.py index ce7b134..75ed618 100644 --- a/vanilla_first_setup/utils/processor.py +++ b/vanilla_first_setup/utils/processor.py @@ -113,7 +113,7 @@ class Processor: f.write("if [ $? -eq 0 ]; then") f.write(f"{out_run}\n") f.write("fi") - + # create the done file f.write("if [ $? -eq 0 ]; then\n") f.write(f"touch {done_file}\n") @@ -141,7 +141,7 @@ class Processor: if os.path.exists(autostart_file): os.remove(autostart_file) - with open(desktop_file, "w") as f: + with open(desktop_file, "w+") as f: f.write("[Desktop Entry]\n") f.write("Name=FirstSetup\n") f.write("Comment=FirstSetup\n") diff --git a/vanilla_first_setup/vanilla-first-setup.gresource.xml b/vanilla_first_setup/vanilla-first-setup.gresource.xml index ff7b364..7cbad4b 100644 --- a/vanilla_first_setup/vanilla-first-setup.gresource.xml +++ b/vanilla_first_setup/vanilla-first-setup.gresource.xml @@ -11,6 +11,7 @@ gtk/default-conn-check.ui gtk/default-theme.ui gtk/default-welcome.ui + gtk/default-user.ui gtk/layout-preferences.ui gtk/layout-yes-no.ui diff --git a/vanilla_first_setup/views/progress.py b/vanilla_first_setup/views/progress.py index 4a74b86..17656c6 100644 --- a/vanilla_first_setup/views/progress.py +++ b/vanilla_first_setup/views/progress.py @@ -33,7 +33,7 @@ class VanillaProgress(Gtk.Box): console_button = Gtk.Template.Child() console_box = Gtk.Template.Child() console_output = Gtk.Template.Child() - + def __init__(self, window, tour: dict, **kwargs): super().__init__(**kwargs) self.__window = window @@ -69,14 +69,14 @@ class VanillaProgress(Gtk.Box): self.__terminal.set_mouse_autohide(True) self.console_output.append(self.__terminal) self.__terminal.connect("child-exited", self.on_vte_child_exited) - + palette = ["#353535", "#c01c28", "#26a269", "#a2734c", "#12488b", "#a347ba", "#2aa1b3", "#cfcfcf", "#5d5d5d", "#f66151", "#33d17a", "#e9ad0c", "#2a7bde", "#c061cb", "#33c7de", "#ffffff"] - + FOREGROUND = palette[0] BACKGROUND = palette[15] FOREGROUND_DARK = palette[15] BACKGROUND_DARK = palette[0] - + self.fg = Gdk.RGBA() self.bg = Gdk.RGBA() @@ -90,7 +90,7 @@ class VanillaProgress(Gtk.Box): self.fg.parse(FOREGROUND_DARK) self.bg.parse(BACKGROUND_DARK) self.__terminal.set_colors(self.fg, self.bg, self.colors) - + for _, tour in self.__tour.items(): self.carousel_tour.append(VanillaTour(self.__window, tour)) @@ -113,18 +113,20 @@ class VanillaProgress(Gtk.Box): time.sleep(5) RunAsync(run_async, None) - + def on_vte_child_exited(self, terminal, status, *args): terminal.get_parent().remove(terminal) status = not bool(status) - + if self.__success_fn is not None and status: - self.__success_fn() + self.__success_fn(*self.__success_fn_args) self.__window.set_installation_result(status, self.__terminal) - def start(self, setup_commands, success_fn): + def start(self, setup_commands, success_fn, *fn_args): self.__success_fn = success_fn + self.__success_fn_args = fn_args + self.__terminal.spawn_async( Vte.PtyFlags.DEFAULT, ".", @@ -136,4 +138,4 @@ class VanillaProgress(Gtk.Box): -1, None, None - ) \ No newline at end of file + ) diff --git a/vanilla_first_setup/window.py b/vanilla_first_setup/window.py index d69f15c..5540e41 100644 --- a/vanilla_first_setup/window.py +++ b/vanilla_first_setup/window.py @@ -173,7 +173,7 @@ class VanillaWindow(Adw.ApplicationWindow): ) self.__view_progress.start(res, Processor.hide_first_setup, self.__user) - + def set_installation_result(self, result, terminal): self.__view_done.set_result(result, terminal) self.next()