17 Commits
0.1.0 ... 0.1.2

Author SHA1 Message Date
Peter
bd90283569 installation: correctly recognize finished (again) 2021-04-18 00:07:56 +02:00
Peter
0281abf136 syscalls: fix method name typo 2021-04-17 23:58:34 +02:00
Peter
9f44d6b798 disk: simplify device info for disk or partition 2021-04-17 23:54:09 +02:00
Peter
576462e3b0 syscalls: add passwordless system calls (can fail) 2021-04-17 23:51:16 +02:00
Peter
2925247a2f update translations 2021-04-17 23:31:24 +02:00
Peter
0af43cbb29 disk: simplify udisks client initialization 2021-04-17 18:29:09 +02:00
Peter
69b7028b9a disk: fix setting stacks 2021-04-17 18:09:33 +02:00
Peter
d498795ac4 disk: do efi check ad-hoc 2021-04-17 18:08:51 +02:00
Peter
59156e2ee1 syscalls: use gsettings to prevent authentication 2021-04-16 16:22:33 +02:00
Peter
4b5ea74094 locale: fix errorprint string 2021-04-16 02:15:09 +02:00
Peter
d62ff1841c failed: suggest internet search 2021-04-16 02:14:03 +02:00
Peter
23f51c1f7e config: store version 2021-04-16 02:13:33 +02:00
Peter
fe9813d495 config: add distribution name 2021-04-16 02:13:02 +02:00
Peter
79ee3c4aaf drop development warning from Readme 2021-04-13 01:24:41 +02:00
Peter
987f193649 system calls: fix setting of locales 2021-04-13 01:21:07 +02:00
Peter
0b06b48c76 mark installation done on fail 2021-04-05 12:23:57 +02:00
Peter
377feea4d3 global state: fix env parameter order 2021-04-03 02:04:07 +02:00
17 changed files with 151 additions and 185 deletions

View File

@@ -1,21 +1,18 @@
# THIS IS STILL UNDER DEVELOPMENT
It can not yet be used to actually install anything.
# OS Installer
A simple operating system installer, intended to be used with live install systems.
Provides bootstrapping through language, keyboard, internet connection and disk selection.
Allows defining of optional additional software to be installed.
# Testing
To try out OS-Installer, without making any changes to your system, run it in debug mode:
# Build and Install
```
meson build
sudo ninja -C build install
os-installer -d
```
# Test
To try OS-Installer, without making any changes to your system, run it in debug mode with `os-installer -d`
# Translating
## Simple Way
* Create an issue [on Github](https://github.com/p3732/os-installer/issues/new) or [on Gitlab](https://gitlab.gnome.org/p3732/os-installer/-/issues/new) stating what language you want to translate `OS-Installer` into

View File

@@ -47,16 +47,21 @@
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes" comments="Shown as confirmation if installation fails. Expression of minor discontent. Underscore can not be the same as for 'Copy Error Text'.">_Meh.</property>
<object class="GtkButton" id="search_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="action-name">app.quit</property>
<property name="use-underline">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes" comments="Shown if installation fails.">Search for Help on the Internet</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
</object>
</child>
<style>
<class name="destructive-action"/>
<class name="large-button"/>
</style>
</object>

View File

@@ -4,6 +4,10 @@
# Place your config under /etc/os-installer/config.yaml for it to be used.
# If a value is not defined, it's default will be used.
# Name of the distribution.
# Default: None
distribution_name: 'Debuntorch'
# Whether the installation needs an internet connection.
# Default: yes
internet_connection_required: yes

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: os-installer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-12 13:40+0100\n"
"POT-Creation-Date: 2021-04-16 01:56+0200\n"
"PO-Revision-Date: 2021-03-12 13:46+0100\n"
"Last-Translator: p3732 <650-p3732@users.noreply.gitlab.gnome.org>\n"
"Language-Team: German <->\n"
@@ -49,26 +49,6 @@ msgstr "Installation _Fortfahren"
msgid "_Stop Installation"
msgstr "Installation _Abbrechen"
#. Shown as simple informative text
#: data/resources/ui/failed_installation_popup.ui
msgid "The installation failed."
msgstr "Die Installation schlug fehl."
#. Shown on button underneath an error text. Underscore can not be the same as for 'Meh'
#: data/resources/ui/failed_installation_popup.ui
msgid "_Copy Error Text"
msgstr "Fehlertext _Kopieren"
#. Shown as confirmation if installation fails. Expression of minor discontent. Underscore can not be the same as for 'Copy Error Text'.
#: data/resources/ui/failed_installation_popup.ui
msgid "_Meh."
msgstr "_Hrmpf."
#. Menu entry to show About dialog for application
#: data/resources/ui/main_window.ui
msgid "_About OS-Installer"
msgstr "_Über OS-Installer"
#. Followed by a disk or partition name
#: data/resources/ui/pages/confirm.ui
msgid "This will delete all data on"
@@ -80,16 +60,16 @@ msgstr "Hiermit werden alle Dateien gelöscht von"
msgid "_Confirm"
msgstr "_Bestätigen"
#. Label to go back to overview of all disks
#: data/resources/ui/pages/disk.ui
msgid "Use Whole Disk"
msgstr "Nutze gesamtes Laufwerk"
#. Explanation for disk selection page.
#: data/resources/ui/pages/disk.ui
msgid "Select Drive for Installation"
msgstr "Wähle ein Laufwerk zur Installation"
#. Label to go back to overview of all disks
#: data/resources/ui/pages/disk.ui
msgid "Use Whole Disk"
msgstr "Nutze gesamtes Laufwerk"
#. Button label to open disk management tool. Underscore can not be for same as for 'Reload'
#: data/resources/ui/pages/disk.ui
msgid "_Manage Disks"
@@ -140,6 +120,16 @@ msgstr ""
msgid "_Continue"
msgstr "_Weiter"
#. Shown as simple informative text
#: data/resources/ui/pages/failed.ui
msgid "The installation failed"
msgstr "Die Installation schlug fehl"
#. Shown if installation fails.
#: data/resources/ui/pages/failed.ui
msgid "Search for Help on the Internet"
msgstr "Suche im Internet nach Hilfe"
#. Hover information on toggle button that shows terminal output during installation.
#: data/resources/ui/pages/install.ui
msgid "Show Terminal Output"
@@ -278,15 +268,3 @@ msgstr ""
#: data/resources/ui/widgets/no_partitions_row.ui
msgid " • BIOS boot partition"
msgstr " • BIOS Boot-Partition"
#~ msgid "Select Locale for Formats"
#~ msgstr "Wähle Lokalisierung für Formate"
#~ msgid "_Reload"
#~ msgstr "_Neuladen"
#~ msgid "Use Different Disk"
#~ msgstr "Wähle ein anderes Laufwerk"
#~ msgid "Showing Layouts for"
#~ msgstr "Tastaturbelelgungen für"

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: os-installer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-12 13:40+0100\n"
"POT-Creation-Date: 2021-04-16 01:56+0200\n"
"PO-Revision-Date: 2021-03-12 13:45+0100\n"
"Last-Translator: p3732 <650-p3732@users.noreply.gitlab.gnome.org>\n"
"Language-Team: German <->\n"
@@ -50,26 +50,6 @@ msgstr "_Nastavi instalaciju"
msgid "_Stop Installation"
msgstr "_Prekini instalaciju"
#. Shown as simple informative text
#: data/resources/ui/failed_installation_popup.ui
msgid "The installation failed."
msgstr "Instalacija nije upijela."
#. Shown on button underneath an error text. Underscore can not be the same as for 'Meh'
#: data/resources/ui/failed_installation_popup.ui
msgid "_Copy Error Text"
msgstr "_Kopiraj tekst pogreške"
#. Shown as confirmation if installation fails. Expression of minor discontent. Underscore can not be the same as for 'Copy Error Text'.
#: data/resources/ui/failed_installation_popup.ui
msgid "_Meh."
msgstr "_Ugh."
#. Menu entry to show About dialog for application
#: data/resources/ui/main_window.ui
msgid "_About OS-Installer"
msgstr "_Više o OS-Instaleru"
#. Followed by a disk or partition name
#: data/resources/ui/pages/confirm.ui
msgid "This will delete all data on"
@@ -81,16 +61,16 @@ msgstr "Ovo će obrisati sve podatke na"
msgid "_Confirm"
msgstr "_Potvrdi"
#. Label to go back to overview of all disks
#: data/resources/ui/pages/disk.ui
msgid "Use Whole Disk"
msgstr "Koristi cijeli disk"
#. Explanation for disk selection page.
#: data/resources/ui/pages/disk.ui
msgid "Select Drive for Installation"
msgstr "Izaberite disk za instalaciju"
#. Label to go back to overview of all disks
#: data/resources/ui/pages/disk.ui
msgid "Use Whole Disk"
msgstr "Koristi cijeli disk"
#. Button label to open disk management tool. Underscore can not be for same as for 'Reload'
#: data/resources/ui/pages/disk.ui
msgid "_Manage Disks"
@@ -140,6 +120,16 @@ msgstr ""
msgid "_Continue"
msgstr "_Nastavi"
#. Shown as simple informative text
#: data/resources/ui/pages/failed.ui
msgid "The installation failed"
msgstr "Instalacija nije upijela"
#. Shown if installation fails.
#: data/resources/ui/pages/failed.ui
msgid "Search for Help on the Internet"
msgstr "Potraži pomoć na Internetu"
#. Hover information on toggle button that shows terminal output during installation.
#: data/resources/ui/pages/install.ui
msgid "Show Terminal Output"
@@ -278,18 +268,3 @@ msgstr ""
#: data/resources/ui/widgets/no_partitions_row.ui
msgid " • BIOS boot partition"
msgstr " • BIOS particija za pokretanje"
#~ msgid "Select Locale for Formats"
#~ msgstr "Odaberite Locale za formate"
#~ msgid "_Reload"
#~ msgstr "_Ponovno učitaj"
#~ msgid "Use Different Disk"
#~ msgstr "Izaberi drugi disk"
#~ msgid "All data on"
#~ msgstr "Svi podaci na"
#~ msgid "will be deleted"
#~ msgstr "bit će obrisani"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: os-installer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-12 13:40+0100\n"
"POT-Creation-Date: 2021-04-16 01:56+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -45,26 +45,6 @@ msgstr ""
msgid "_Stop Installation"
msgstr ""
#. Shown as simple informative text
#: data/resources/ui/failed_installation_popup.ui
msgid "The installation failed."
msgstr ""
#. Shown on button underneath an error text. Underscore can not be the same as for 'Meh'
#: data/resources/ui/failed_installation_popup.ui
msgid "_Copy Error Text"
msgstr ""
#. Shown as confirmation if installation fails. Expression of minor discontent. Underscore can not be the same as for 'Copy Error Text'.
#: data/resources/ui/failed_installation_popup.ui
msgid "_Meh."
msgstr ""
#. Menu entry to show About dialog for application
#: data/resources/ui/main_window.ui
msgid "_About OS-Installer"
msgstr ""
#. Followed by a disk or partition name
#: data/resources/ui/pages/confirm.ui
msgid "This will delete all data on"
@@ -76,16 +56,16 @@ msgstr ""
msgid "_Confirm"
msgstr ""
#. Label to go back to overview of all disks
#: data/resources/ui/pages/disk.ui
msgid "Use Whole Disk"
msgstr ""
#. Explanation for disk selection page.
#: data/resources/ui/pages/disk.ui
msgid "Select Drive for Installation"
msgstr ""
#. Label to go back to overview of all disks
#: data/resources/ui/pages/disk.ui
msgid "Use Whole Disk"
msgstr ""
#. Button label to open disk management tool. Underscore can not be for same as for 'Reload'
#: data/resources/ui/pages/disk.ui
msgid "_Manage Disks"
@@ -134,6 +114,16 @@ msgstr ""
msgid "_Continue"
msgstr ""
#. Shown as simple informative text
#: data/resources/ui/pages/failed.ui
msgid "The installation failed"
msgstr ""
#. Shown if installation fails.
#: data/resources/ui/pages/failed.ui
msgid "Search for Help on the Internet"
msgstr ""
#. Hover information on toggle button that shows terminal output during installation.
#: data/resources/ui/pages/install.ui
msgid "Show Terminal Output"

View File

@@ -30,7 +30,10 @@ def _get_fallback_config():
'suggested_languages': ['en', 'ar', 'de', 'es', 'fr', 'ja', 'ru', 'zh'],
'minimum_disk_size': 5,
'offer_disk_encryption': True,
'additional_software': {}
'additional_software': {},
'version': -1,
'distribution_name': None
}

View File

@@ -33,7 +33,7 @@ class GlobalState:
print('Installation failed before window initalization done!')
def create_envs(self, with_install_envs=False, with_configure_envs=False):
return create_envs(self.config, with_configure_envs, with_install_envs)
return create_envs(self.config, with_install_envs, with_configure_envs)
global_state = GlobalState()

View File

@@ -32,10 +32,12 @@ class Application(Gtk.Application):
# Connect app shutdown signal
self.connect('shutdown', self._on_quit)
# Add --hidden command line option
# Additional command line options
self.add_main_option('demo-mode', b'd', GLib.OptionFlags.NONE,
GLib.OptionArg.NONE, "Run in demo mode, don't alter the system", None)
global_state.set_config('version', version)
def _load_css(self):
css_provider = Gtk.CssProvider()
css_provider.load_from_resource('/com/github/p3732/os-installer/css/style.css')

View File

@@ -8,7 +8,7 @@ from .disk_provider import disk_provider
from .global_state import global_state
from .installation_scripting import installation_scripting
from .page import Page
from .system_calls import has_efi_vars, open_disks
from .system_calls import is_booted_with_uefi, open_disks
from .widgets import DeviceRow, NoPartitionsRow, empty_list
GIGABYTE_FACTOR = 1024 * 1024 * 1024
@@ -50,8 +50,9 @@ class DiskPage(Gtk.Box, Page):
self.settings_button.connect('clicked', self._on_clicked_disks_button)
self.refresh_button.connect('clicked', self._on_clicked_reload_button)
# start gl checking
self.uses_uefi = has_efi_vars()
def _set_stacks(self, state):
self.list_stack.set_visible_child_name(state)
self.text_stack.set_visible_child_name(state)
def _setup_disk_list(self):
# clear list
@@ -61,42 +62,40 @@ class DiskPage(Gtk.Box, Page):
disks = disk_provider.get_disks()
for disk_info in disks:
too_small = disk_info.size < self.minimum_disk_size
row = DeviceRow('disk', disk_info, too_small)
row = DeviceRow(disk_info, too_small)
self.disk_list.add(row)
# show
self.list_stack.set_visible_child_name('disks')
self._set_stacks('disks')
def _setup_partition_list(self, disk_info):
self.current_disk = disk_info
empty_list(self.partition_list)
# efi vars
disk_uefi_okay = not self.uses_uefi or disk_info.efi_partition
# set disk info
self.disk_label.set_label(disk_info.name)
self.disk_device_path.set_label(disk_info.device_path)
self.disk_size.set_label(disk_info.size_text)
# fill partition list
if disk_uefi_okay:
disk_uefi_okay = not is_booted_with_uefi() or disk_info.efi_partition
if disk_uefi_okay and len(disk_info.partitions) > 0:
for partition_info in disk_info.partitions:
too_small = partition_info.size < self.minimum_disk_size
row = DeviceRow('partition', partition_info, too_small)
row = DeviceRow(partition_info, too_small)
self.partition_list.add(row)
else:
self.partition_list.add(NoPartitionsRow())
# show
self.list_stack.set_visible_child_name('partitions')
self._set_stacks('partitions')
def _store_device_info(self, info):
global_state.set_config('disk_name', info.name)
global_state.set_config('disk_device_path', info.device_path)
global_state.set_config('disk_is_partition', info.is_partition)
global_state.set_config('disk_efi_partition', info.efi_partition)
global_state.set_config('disk_is_partition', not type(info) == type(self.current_disk))
global_state.set_config('disk_efi_partition', self.current_disk.efi_partition)
### callbacks ###
@@ -136,6 +135,6 @@ class DiskPage(Gtk.Box, Page):
self.can_navigate_backward = False
if not self.lock.acquire(blocking=False):
return
self.list_stack.set_visible_child_name('disks')
self.text_stack.set_visible_child_name('disks')
self._set_stacks('disks')
self.lock.release()

View File

@@ -3,6 +3,7 @@
from gi.repository import Gtk
from .installation_scripting import installation_scripting
from .system_calls import open_internet_search
from .page import Page
@@ -12,9 +13,16 @@ class FailedPage(Gtk.Box, Page):
image_name = 'computer-fail-symbolic'
terminal_box = Gtk.Template.Child()
search_button = Gtk.Template.Child()
def __init__(self, **kwargs):
Gtk.Box.__init__(self, **kwargs)
self.search_button.connect('clicked', self._on_search_button_clicked)
### callbacks ###
def _on_search_button_clicked(self, button):
open_internet_search()
### public methods ###

View File

@@ -117,7 +117,7 @@ class LocalePage(Gtk.Box, Page):
if timezone:
self._set_timezone(timezone)
elif list_box == self.subzones_list:
print('Subzone', subzone, 'does not have any timezone attached to it! Falling back to UTC.')
print('Subzone', location.get_name(), 'does not have any timezone attached to it! Falling back to UTC.')
self._set_timezone('UTC')
elif list_box == self.continents_list:
self._load_countries_list(location)

View File

@@ -23,18 +23,19 @@ class DeviceRow(Gtk.ListBoxRow):
arrow_stack = Gtk.Template.Child()
device_path = Gtk.Template.Child()
def __init__(self, row_type, info, too_small, **kwargs):
def __init__(self, info, too_small, **kwargs):
super().__init__(**kwargs)
self.info = info
self.size.set_label(info.size_text)
self.device_path.set_label(info.device_path)
self.name_stack.set_visible_child_name(row_type)
if 'disk' == row_type:
if hasattr(info, 'partitions'):
self.name_stack.set_visible_child_name('disk')
if info.name:
self.disk_name.set_label(info.name)
elif 'partition' == row_type:
else:
self.name_stack.set_visible_child_name('partition')
if not info.prefixed:
info.name = self.partition_name.get_label() + ' ' + info.name
info.prefixed = True

View File

@@ -194,6 +194,8 @@ class OsInstallerWindow(Handy.ApplicationWindow):
def show_failed_page(self):
with self.navigation_lock:
global_state.installation_running = False
failed_page_position = len(self.available_pages)-1
self.navigation_state.earliest = failed_page_position
self._load_page(failed_page_position)

View File

@@ -72,10 +72,13 @@ class InstallationScripting():
if not status == 0 and not global_state.demo_mode:
global_state.installation_failed()
elif self.current_step == 3 and global_state.demo_mode:
global_state.advance(self.install_page_name)
elif self.current_step == 3:
global_state.advance_without_return(self.install_page_name)
global_state.installation_running = False
if global_state.demo_mode:
# allow returning in demo
global_state.advance(self.install_page_name)
else:
global_state.advance_without_return(self.install_page_name)
else:
self._start_next_script()

View File

@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from gi.repository import GLib, UDisks
from gi.repository import GLib, GObject, UDisks
EFI_PARTITION_GUID = 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B'
EFI_PARTITON_FLAGS = UDisks.PartitionTypeInfoFlags.SYSTEM.numerator
@@ -8,25 +8,22 @@ EFI_PARTITON_FLAGS = UDisks.PartitionTypeInfoFlags.SYSTEM.numerator
class DeviceInfo:
device_path: str
efi_partition: str = ''
is_partition: bool = True
name: str
prefixed: bool = False
size: int
size_text: str
class DeviceWithTableInfo(DeviceInfo):
is_gpt: bool
class Disk(DeviceInfo):
has_table: bool = False
is_msdos: bool = False
is_gpt: bool = False
partitions: list = []
def __init__(self):
self.is_partition = False
efi_partition: str = ''
class DiskProvider:
def __init__(self):
self.udisks_client = None
udisks_client = UDisks.Client.new_sync()
def _get_one_partition(self, partition, block):
# partition info
@@ -62,27 +59,31 @@ class DiskProvider:
else:
print('Unhandled partiton in partition table, ignoring.')
# set efi partition
for partition in partitions:
partition.efi_partition = efi_partition
return (partitions, efi_partition)
def _get_disk_info(self, block, drive, partition_table):
# disk info
disk_info = DeviceWithTableInfo()
disk_info = Disk()
disk_info.name = (drive.props.vendor + ' ' + drive.props.model).strip()
disk_info.size = block.props.size
disk_info.size_text = self._size_to_str(disk_info.size)
disk_info.device_path = block.props.device
disk_info.is_gpt = 'gpt' == partition_table.props.type
# partitions
disk_info.partitions, disk_info.efi_partition = self._get_partitions(partition_table, disk_info)
if partition_table:
disk_info.has_table = True
disk_info.is_gpt = 'gpt' == partition_table.props.type
disk_info.is_msdos = 'msdos' == partition_table.props.type
disk_info.partitions, disk_info.efi_partition = self._get_partitions(partition_table, disk_info)
return disk_info
def _get_available_disks(self):
def _size_to_str(self, size):
return self.udisks_client.get_size_for_display(size, False, False)
### public methods ###
def get_disks(self):
disks = []
# get available devices
@@ -93,9 +94,13 @@ class DiskProvider:
for device in devices:
udisks_object = self.udisks_client.get_object(device)
if udisks_object:
partition = udisks_object.get_partition()
if partition:
continue # skip partitions
block = udisks_object.get_block()
partition_table = udisks_object.get_partition_table()
if block and partition_table:
if block:
drive = self.udisks_client.get_drive_for_block(block)
if drive:
disk_info = self._get_disk_info(block, drive, partition_table)
@@ -103,17 +108,5 @@ class DiskProvider:
return disks
def _size_to_str(self, size):
return self.udisks_client.get_size_for_display(size, False, False)
### public methods ###
def get_disks(self):
if not self.udisks_client:
self.udisks_client = UDisks.Client.new_sync()
# get current disks information via udisks
return self._get_available_disks()
disk_provider = DiskProvider()

View File

@@ -22,7 +22,7 @@ def _run_program(args):
### public methods ###
def has_efi_vars():
def is_booted_with_uefi():
return os.path.isdir("/sys/firmware/efi/efivars")
@@ -30,6 +30,14 @@ def open_disks():
_run_program(['gnome-disks'])
def open_internet_search():
distribution_name = global_state.get_config('distribution_name')
name_snippet = '"' + distribution_name + '" ' if distribution_name else ''
search_text = '{}"failed installation" "os-installer version {}"'.format(
name_snippet, global_state.get_config('version'))
_run_program(['epiphany', '--search', search_text])
def open_wifi_settings():
_run_program(['gnome-control-center', 'wifi'])
@@ -62,24 +70,22 @@ def set_system_language(language_info):
# fallback
Locale.setlocale(Locale.LC_ALL, 'en_US.UTF-8')
# set system locale
_exec(['localectl', 'set-locale', locale])
# TODO find correct way to set system locale without user authentication
_exec(['localectl', '--no-ask-password', 'set-locale', 'LANG=en_US.UTF-8'])
def set_system_formats(locale):
global_state.set_config('formats', locale)
_exec(['localectl', 'set-locale', 'LC_NUMERIC', locale])
_exec(['localectl', 'set-locale', 'LC_TIME', locale])
_exec(['localectl', 'set-locale', 'LC_MONETARY', locale])
_exec(['localectl', 'set-locale', 'LC_PAPER', locale])
_exec(['localectl', 'set-locale', 'LC_MEASUREMENT', locale])
_exec(['gsettings', 'set', 'org.gnome.system.locale', 'region', "'{}'".format(locale)])
def set_system_timezone(timezone):
global_state.set_config('timezone', timezone)
_exec(['timedatectl', 'set-timezone', timezone])
# TODO find correct way to set timezone without user authentication
_exec(['timedatectl', '--no-ask-password', 'set-timezone', timezone])
def start_system_timesync():
_exec(['timedatectl', 'set-ntp', 'true'])
# TODO find correct way to set enable time sync without user authentication
_exec(['timedatectl', '--no-ask-password', 'set-ntp', 'true'])
_exec(['gsettings', 'set', 'org.gnome.desktop.datetime', 'automatic-timezone', 'true'])