2 # -*- coding: utf-8 -*-
6 # Copyright © 2013-2018 RebornOS
8 # This file is part of Cnchi.
10 # Cnchi is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
15 # Cnchi is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # The following additional terms are in effect as per Section 7 of the license:
22 # The preservation of all legal notices and author attributions in
23 # the material or in the Appropriate Legal Notices displayed
24 # by works containing it is required.
26 # You should have received a copy of the GNU General Public License
27 # along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
30 """ Main Cnchi Window """
33 import multiprocessing
39 import misc.extra as misc
50 import pages.user_info
55 import pages.automatic
56 import pages.alongside
61 gi.require_version('Gtk', '3.0')
62 from gi.repository import Gtk, Gdk
65 # When testing, no _() is available
68 except NameError as err:
72 def atk_set_image_description(widget, description):
73 """ Sets the textual description for a widget that displays image/pixmap
74 information onscreen. """
75 atk_widget = widget.get_accessible()
76 if atk_widget is not None:
77 atk_widget.set_object_description(description)
80 def atk_set_object_description(widget, description):
81 """ Sets the textual description for a widget """
82 atk_widget = widget.get_accessible()
83 if atk_widget is not None:
84 atk_widget.set_image_description(description)
88 class MainWindow(Gtk.ApplicationWindow):
89 """ Cnchi main window """
91 def __init__(self, app, cmd_line):
92 Gtk.ApplicationWindow.__init__(self, title="Cnchi", application=app)
94 self._main_window_width = 875
95 self._main_window_height = 550
97 logging.info("Cnchi installer version %s", info.CNCHI_VERSION)
99 self.settings = config.Settings()
100 self.gui_dir = self.settings.get('ui')
102 if not os.path.exists(self.gui_dir):
103 cnchi_dir = os.path.join(os.path.dirname(__file__), './')
104 self.settings.set('cnchi', cnchi_dir)
106 gui_dir = os.path.join(os.path.dirname(__file__), 'ui/')
107 self.settings.set('ui', gui_dir)
109 data_dir = os.path.join(os.path.dirname(__file__), 'data/')
110 self.settings.set('data', data_dir)
112 self.gui_dir = self.settings.get('ui')
114 # By default, always try to use local /var/cache/pacman/pkg
115 xz_cache = ["/var/cache/pacman/pkg"]
118 if cmd_line.cache and cmd_line.cache not in xz_cache:
119 xz_cache.append(cmd_line.cache)
122 for xz_path in xz_cache:
124 "Cnchi will use '%s' as a source for cached xz packages",
127 # Store cache dirs in config
128 self.settings.set('xz_cache', xz_cache)
130 data_dir = self.settings.get('data')
132 # For things we are not ready for users to test
133 self.settings.set('hidden', cmd_line.hidden)
136 self.settings.set('a11y', cmd_line.a11y)
138 # Set enabled desktops
139 if self.settings.get('hidden'):
140 self.settings.set('desktops', desktop_info.DESKTOPS_DEV)
141 elif self.settings.get('a11y'):
142 self.settings.set('desktops', desktop_info.DESKTOPS_A11Y)
144 self.settings.set('desktops', desktop_info.DESKTOPS)
146 if cmd_line.environment:
147 my_desktop = cmd_line.environment.lower()
148 if my_desktop in desktop_info.DESKTOPS:
149 self.settings.set('desktop', my_desktop)
150 self.settings.set('desktop_ask', False)
152 "Cnchi will install the %s desktop environment",
155 self.cnchi_ui = Gtk.Builder()
156 path = os.path.join(self.gui_dir, "cnchi.ui")
157 self.cnchi_ui.add_from_file(path)
159 main = self.cnchi_ui.get_object("main")
160 # main.set_property("halign", Gtk.Align.CENTER)
163 self.header_ui = Gtk.Builder()
164 path = os.path.join(self.gui_dir, "header.ui")
165 self.header_ui.add_from_file(path)
166 self.header = self.header_ui.get_object("header")
168 self.logo = self.header_ui.get_object("logo")
170 data_dir, "images", "antergos", "rebornos-logo-mini2.png")
171 self.logo.set_from_file(path)
174 self.header.set_name("header")
175 self.logo.set_name("logo")
177 self.main_box = self.cnchi_ui.get_object("main_box")
178 # self.main_box.set_property("halign", Gtk.Align.CENTER)
180 ##self.main_box.set_property('width_request', 800)
182 self.progressbar = self.cnchi_ui.get_object("main_progressbar")
183 self.progressbar.set_name('process_progressbar')
185 self.progressbar.set_can_focus(False)
187 self.forward_button = self.header_ui.get_object("forward_button")
188 self.backwards_button = self.header_ui.get_object("backwards_button")
190 # atk_set_image_description(self.forward_button, _("Next step"))
191 # atk_set_image_description(self.backwards_button, _("Previous step"))
192 # atk_set_object_description(self.forward_button, _("Next step"))
193 # atk_set_object_description(self.backwards_button, _("Previous step"))
195 self.forward_button.set_name('fwd_btn')
196 self.forward_button.set_always_show_image(True)
198 self.backwards_button.set_name('bk_btn')
199 self.backwards_button.set_always_show_image(True)
203 self.forward_button.set_label(_("Next"))
204 self.backwards_button.set_label(_("Back"))
206 # Create a queue. Will be used to report pacman messages
207 # (pacman/pac.py) to the main thread (installation/process.py)
208 self.callback_queue = multiprocessing.JoinableQueue()
210 if cmd_line.packagelist:
211 self.settings.set('alternate_package_list', cmd_line.packagelist)
213 "Using '%s' file as package list",
214 self.settings.get('alternate_package_list'))
216 self.set_titlebar(self.header)
218 # Prepare params dict to pass common parameters to all screens
220 self.params['main_window'] = self
221 self.params['header'] = self.header
222 self.params['gui_dir'] = self.gui_dir
223 self.params['forward_button'] = self.forward_button
224 self.params['backwards_button'] = self.backwards_button
225 self.params['callback_queue'] = self.callback_queue
226 self.params['settings'] = self.settings
227 self.params['main_progressbar'] = self.progressbar
229 self.params['checks_are_optional'] = cmd_line.no_check
230 self.params['no_tryit'] = cmd_line.no_tryit
231 self.params['a11y'] = cmd_line.a11y
233 # Just load the first two screens (the other ones will be loaded later)
234 # We do this so the user has not to wait for all the screens to be
237 self.pages["welcome"] = pages.welcome.Welcome(self.params)
239 if os.path.exists('/home/antergos/.config/openbox'):
240 # In minimal iso, load language screen now
241 self.pages["language"] = pages.language.Language(self.params)
243 # Fix bugy Gtk window size when using Openbox
244 self._main_window_width = 750
245 self._main_window_height = 450
247 self.connect('delete-event', self.on_exit_button_clicked)
248 self.connect('key-release-event', self.on_key_release)
250 self.cnchi_ui.connect_signals(self)
251 self.header_ui.connect_signals(self)
253 nil, major, minor = info.CNCHI_VERSION.split('.')
255 title_string = "{0} {1}.{2}.{3}".format(name, nil, major, minor)
256 self.set_title(title_string)
257 self.header.set_title(title_string)
258 self.header.set_subtitle(_("RebornOS Installer"))
259 self.header.set_show_close_button(True)
260 self.tooltip_string = "{0} {1}.{2}.{3}".format(name, nil, major, minor)
261 self.header.forall(self.header_for_all_callback, self.tooltip_string)
266 icon_path = os.path.join(
271 self.set_icon_from_file(icon_path)
273 # Set the first page to show
275 # If minimal iso is detected, skip the welcome page.
276 if os.path.exists('/home/antergos/.config/openbox'):
277 self.current_page = self.pages["language"]
278 self.settings.set('timezone_start', True)
280 self.current_page = self.pages["welcome"]
282 self.main_box.add(self.current_page)
285 style_provider = Gtk.CssProvider()
287 style_css = os.path.join(data_dir, "css", "gtk-style.css")
289 with open(style_css, 'rb') as css:
290 css_data = css.read()
292 style_provider.load_from_data(css_data)
294 Gtk.StyleContext.add_provider_for_screen(
295 Gdk.Screen.get_default(), style_provider,
296 Gtk.STYLE_PROVIDER_PRIORITY_USER
302 self.current_page.prepare('forwards')
304 # Hide backwards button
305 self.backwards_button.hide()
307 self.progressbar.set_fraction(0)
308 self.progressbar_step = 0
310 # Do not hide progress bar for minimal iso as it would break
311 # the widget alignment on language page.
312 if not os.path.exists('/home/antergos/.config/openbox'):
314 self.progressbar.hide()
320 def header_for_all_callback(self, widget, _data):
321 """ Show tooltip in header """
322 if isinstance(widget, Gtk.Box):
323 widget.forall(self.header_for_all_callback, self.tooltip_string)
324 elif widget.get_style_context().has_class('title'):
325 widget.set_tooltip_text(self.tooltip_string)
327 def load_pages(self):
328 """ Preload all installer pages """
329 if not os.path.exists('/home/antergos/.config/openbox'):
330 self.pages["language"] = pages.language.Language(self.params)
332 self.pages["check"] = pages.check.Check(self.params)
333 self.pages["location"] = pages.location.Location(self.params)
335 self.pages["timezone"] = pages.timezone.Timezone(self.params)
337 if self.settings.get('desktop_ask'):
338 self.pages["keymap"] = pages.keymap.Keymap(self.params)
339 self.pages["desktop"] = pages.desktop.DesktopAsk(self.params)
340 self.pages["features"] = pages.features.Features(self.params)
342 self.pages["keymap"] = pages.keymap.Keymap(
344 next_page='features')
345 self.pages["features"] = pages.features.Features(
349 self.pages["cache"] = pages.cache.Cache(self.params)
350 self.pages["mirrors"] = pages.mirrors.Mirrors(self.params)
352 self.pages["installation_ask"] = pages.ask.InstallationAsk(
354 self.pages["installation_automatic"] = pages.automatic.InstallationAutomatic(
357 if self.settings.get("enable_alongside"):
358 self.pages["installation_alongside"] = pages.alongside.InstallationAlongside(
361 self.pages["installation_alongside"] = None
363 self.pages["installation_advanced"] = pages.advanced.InstallationAdvanced(
365 self.pages["installation_zfs"] = pages.zfs.InstallationZFS(
367 self.pages["user_info"] = pages.user_info.UserInfo(self.params)
368 self.pages["summary"] = pages.summary.Summary(self.params)
369 self.pages["slides"] = pages.slides.Slides(self.params)
372 if os.path.exists('/home/antergos/.config/openbox'):
373 # In minimal (openbox) we don't have a welcome screen
376 num_pages = len(self.pages) - diff
379 self.progressbar_step = 1.0 / num_pages
381 def set_geometry(self):
382 """ Sets Cnchi window geometry """
383 self.set_position(Gtk.WindowPosition.CENTER)
384 self.set_resizable(False)
386 (min_width, natural_width) = self.get_preferred_width()
387 (min_height, natural_height) = self.get_preferred_width()
388 logging.debug("Main window minimal size: %dx%d", min_width, min_height)
389 logging.debug("Main window natural size: %dx%d", natural_width, natural_height)
390 logging.debug("Setting main window size to %dx%d",
391 self._main_window_width, self._main_window_height)
393 self.set_size_request(self._main_window_width,
394 self._main_window_height)
395 self.set_default_size(self._main_window_width,
396 self._main_window_height)
398 geom = Gdk.Geometry()
399 geom.min_width = self._main_window_width
400 geom.min_height = self._main_window_height
401 geom.max_width = self._main_window_width
402 geom.max_height = self._main_window_height
403 geom.base_width = self._main_window_width
404 geom.base_height = self._main_window_height
408 hints = (Gdk.WindowHints.MIN_SIZE |
409 Gdk.WindowHints.MAX_SIZE |
410 Gdk.WindowHints.BASE_SIZE |
411 Gdk.WindowHints.RESIZE_INC)
413 self.set_geometry_hints(None, geom, hints)
415 def on_key_release(self, _widget, event, _data=None):
416 """ Callback called when a key is released """
417 if event.keyval == Gdk.keyval_from_name('Escape'):
418 response = self.confirm_quitting()
419 if response == Gtk.ResponseType.YES:
420 self.on_exit_button_clicked(self)
423 def confirm_quitting(self):
424 """ Shows confirmation message before quitting """
425 message = Gtk.MessageDialog(
428 destroy_with_parent=True,
429 message_type=Gtk.MessageType.QUESTION,
430 buttons=Gtk.ButtonsType.YES_NO,
431 text=_("Do you really want to quit the installer?"))
432 response = message.run()
436 def on_exit_button_clicked(self, _widget, _data=None):
439 misc.remove_temp_files(self.settings.get('temp'))
440 logging.info("Quiting installer...")
441 for proc in multiprocessing.active_children():
444 except KeyboardInterrupt:
447 def set_progressbar_step(self, add_value):
448 """ Update progress bar """
449 new_value = self.progressbar.get_fraction() + add_value
454 self.progressbar.set_fraction(new_value)
456 self.progressbar.show()
458 self.progressbar.hide()
460 def on_forward_button_clicked(self, _widget, _data=None):
461 """ Show next screen """
462 next_page = self.current_page.get_next_page()
464 if next_page is not None:
466 if next_page not in self.pages.keys():
469 self.progressbar_step = 1.0 / (len(self.pages) - 2)
471 stored = self.current_page.store_values()
474 self.set_progressbar_step(self.progressbar_step)
475 self.main_box.remove(self.current_page)
477 self.current_page = self.pages[next_page]
479 if self.current_page is not None:
480 self.current_page.prepare('forwards')
481 self.main_box.add(self.current_page)
482 if self.current_page.get_prev_page() is not None:
483 # There is a previous page, show back button
484 self.backwards_button.show()
485 self.backwards_button.set_sensitive(True)
487 # We can't go back, hide back button
488 self.backwards_button.hide()
489 if self.current_page == "slides":
490 # Show logo in slides screen
493 def on_backwards_button_clicked(self, _widget, _data=None):
494 """ Show previous screen """
495 prev_page = self.current_page.get_prev_page()
497 if prev_page is not None:
498 self.set_progressbar_step(-self.progressbar_step)
500 # If we go backwards, don't store user changes
501 # self.current_page.store_values()
503 self.main_box.remove(self.current_page)
505 self.current_page = self.pages[prev_page]
507 if self.current_page is not None:
508 self.current_page.prepare('backwards')
509 self.main_box.add(self.current_page)
511 if self.current_page.get_prev_page() is None:
512 # We're at the first page
513 self.backwards_button.hide()
514 self.progressbar.hide()