class AudioTemplate(Gtk.Box): """ Template for audio screens """ def __init__(self, img_path, title, description): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) if img_path: self.image = Gtk.Image.new_from_file(img_path) self.pack_start(self.image, False, False, 0) self.heading = Heading(title, description) icon_path = os.path.join(MEDIA_DIR, "play-sound.png") self.kano_button = KanoButton(text="PLAY SOUND", color="blue", icon_filename=icon_path) self.kano_button.pack_and_align() self.kano_button.set_margin_top(10) self.pack_start(self.heading.container, False, False, 0) self.pack_start(self.kano_button.align, False, False, 0) button_box = Gtk.ButtonBox(spacing=15) button_box.set_layout(Gtk.ButtonBoxStyle.CENTER) self.yes_button = KanoButton("YES") self.yes_button.set_sensitive(False) self.no_button = KanoButton("NO", color="red") self.no_button.set_sensitive(False) button_box.pack_start(self.yes_button, False, False, 0) button_box.pack_start(self.no_button, False, False, 0) button_box.set_margin_bottom(5) self.pack_start(button_box, False, False, 15)
class CharacterEdit(Gtk.EventBox): '''Offer the user the option to modify their avatar ''' def __init__(self, win, char_creator): Gtk.EventBox.__init__(self) self._win = win # Should this be inherited, passed as a variable, or global? # Could be a member variable in window. # self.char_creator = self._win.get_char_creator() self.char_creator = char_creator self._win.pack_in_main_content(self.char_creator) self.char_creator.reset_selected_menu_items() self._save_changes_button = KanoButton(_("Save changes").upper()) self._save_changes_button.connect('clicked', self.save_changes) self._save_changes_button.set_sensitive(False) self.char_creator.connect( 'character_changed', self._make_button_sensitive ) discard_changes_button = OrangeButton(_("Discard").upper()) discard_changes_button.connect('clicked', self.discard) discard_changes_button.set_margin_left(100) empty_label = Gtk.Label("") button_box = Gtk.ButtonBox() button_box.pack_start(discard_changes_button, False, False, 0) button_box.pack_start(self._save_changes_button, False, False, 0) button_box.pack_start(empty_label, False, False, 0) self._win.pack_in_bottom_bar(button_box) self._win.show_all() # Hide all the pop ups self.char_creator._hide_pop_ups() def save_changes(self, widget): self.char_creator.save() self._go_back_to_display_screen() def discard(self, widget): self.char_creator.update_from_saved_image() self._go_back_to_display_screen() def _go_back_to_display_screen(self): '''Don't save, just go back to the edit character screen ''' self._win.empty_main_content() self._win.empty_bottom_bar() self._win.menu_bar.enable_buttons() CharacterDisplay(self._win) def _make_button_sensitive(self, widget=None): self._save_changes_button.set_sensitive(True)
class CharacterEdit(Gtk.EventBox): '''Offer the user the option to modify their avatar ''' def __init__(self, win, char_creator): Gtk.EventBox.__init__(self) self._win = win # Should this be inherited, passed as a variable, or global? # Could be a member variable in window. # self.char_creator = self._win.get_char_creator() self.char_creator = char_creator self._win.pack_in_main_content(self.char_creator) self.char_creator.reset_selected_menu_items() self._save_changes_button = KanoButton(_("Save changes").upper()) self._save_changes_button.connect('clicked', self.save_changes) self._save_changes_button.set_sensitive(False) self.char_creator.connect('character_changed', self._make_button_sensitive) discard_changes_button = OrangeButton(_("Discard").upper()) discard_changes_button.connect('clicked', self.discard) discard_changes_button.set_margin_left(100) empty_label = Gtk.Label("") button_box = Gtk.ButtonBox() button_box.pack_start(discard_changes_button, False, False, 0) button_box.pack_start(self._save_changes_button, False, False, 0) button_box.pack_start(empty_label, False, False, 0) self._win.pack_in_bottom_bar(button_box) self._win.show_all() # Hide all the pop ups self.char_creator._hide_pop_ups() def save_changes(self, widget): self.char_creator.save() self._go_back_to_display_screen() def discard(self, widget): self.char_creator.update_from_saved_image() self._go_back_to_display_screen() def _go_back_to_display_screen(self): '''Don't save, just go back to the edit character screen ''' self._win.empty_main_content() self._win.empty_bottom_bar() self._win.menu_bar.enable_buttons() CharacterDisplay(self._win) def _make_button_sensitive(self, widget=None): self._save_changes_button.set_sensitive(True)
class CharacterWindow(Gtk.Window): def __init__(self, cb, css_path): super(CharacterWindow, self).__init__() apply_styling_to_screen(css_path) self.get_style_context().add_class("character_window") self.set_decorated(False) self.close_cb = cb self.char_edit = CharacterCreator(randomise=True, no_sync=True) self.char_edit.connect("character_changed", self._make_button_sensitive) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.add(vbox) vbox.pack_start(self.char_edit, False, False, 0) self._kano_button = KanoButton("OK") self._kano_button.connect("clicked", self.close_window) self._kano_button.pack_and_align() self._kano_button.set_sensitive(False) self.connect("delete-event", Gtk.main_quit) self.set_keep_above(True) vbox.pack_start(self._kano_button.align, False, False, 10) self.show_all() self.char_edit.show_pop_up_menu_for_category("judoka-faces") self.char_edit.select_category_button("judoka-faces") def _make_button_sensitive(self, widget=None): self._kano_button.set_sensitive(True) def close_window(self, widget): self.char_edit.save() self.destroy() GLib.idle_add(self.close_cb)
class LoginWithKanoWorldView(Gtk.Grid): def __init__(self, greeter): Gtk.Grid.__init__(self) self.get_style_context().add_class('password') self.set_row_spacing(12) self.greeter = greeter title = Heading(_('Login with Kano World'), _('Enter your Kano World details.')) self.attach(title.container, 0, 0, 1, 1) self.username = Gtk.Entry() self.username.set_placeholder_text('username') self.attach(self.username, 0, 1, 1, 1) self.password = Gtk.Entry() self.password.set_visibility(False) self.password.set_placeholder_text('password') self.attach(self.password, 0, 2, 1, 1) self.login_btn = KanoButton(_('LOGIN')) self.login_btn.connect('clicked', self._btn_login_pressed) self.attach(self.login_btn, 0, 3, 1, 1) def _btn_login_pressed(self, event=None, button=None): ''' Authenticates against Kano World. If successful synchronizes to a local Unix account, and tells lightdm to go forward with local a login. ''' logger.debug('Synchronizing Kano World account') self.login_btn.start_spinner() self.login_btn.set_sensitive(False) t = threading.Thread(target=self._thr_login) t.start() def _thr_login(self): loggedin = False reason = '' # TODO: Disable the "login" button unless these entry fields are non-empty # Collect credentials from the view self.unix_password = self.password.get_text() self.world_username = self.username.get_text() self.unix_username = self.username.get_text() atsign = self.unix_username.find('@') if atsign != -1: # For if we are in "staging" mode (see /etc/kano-world.conf) self.unix_username = self.unix_username[:atsign] # Now try to login to Kano World try: logger.debug('Authenticating user: {} to Kano World'.format(self.username.get_text())) (loggedin, reason) = kano_world_authenticate(self.username.get_text(), self.password.get_text()) logger.debug('Kano World auth response: {} - {}'.format(loggedin, reason)) except Exception as e: reason = str(e) logger.debug('Kano World auth Exception: {}'.format(reason)) pass if not loggedin: # Kano world auth unauthorized # FIXME: Localizing the below string fails with an exception GObject.idle_add(self._error_message_box, 'Failed to authenticate to Kano World', reason) return else: # We are authenticated to Kano World: proceed with forcing local user rc = -1 try: # Create the local unix user, bypass kano-init-flow, login & sync to Kano World createuser_cmd = 'sudo /usr/bin/kano-greeter-account {} {} {}'.format( self.unix_username, self.unix_password, self.world_username) _, _, rc = run_cmd(createuser_cmd) if rc == 0: logger.debug('Local user created correctly: {}'.format(self.unix_username)) elif rc == 1: logger.debug('Local user already exists, proceeding with login: {}'.format(self.unix_username)) created = True except: created = False if not created: logger.debug('Error creating new local user: {}'.format(self.unix_username)) GObject.idle_add(self._error_message_box, "Could not create local user", rc) return # Tell Lidghtdm to proceed with login session using the new user # We bind LightDM at this point only, this minimizes the number of attempts # to bind the Greeter class to a view, which he does not like quite well. logger.debug('Scheduling lightdm authentication in math thread') GObject.idle_add(self._auth_call) def _auth_call(self): logger.debug('Starting lightdm authentication') self._reset_greeter() self.greeter.authenticate(self.unix_username) if self.greeter.get_is_authenticated(): logger.debug('User is already authenticated, starting session') def _reset_greeter(self): # connect signal handlers to LightDM self.cb_one = self.greeter.connect('show-prompt', self._send_password_cb) self.cb_two = self.greeter.connect('authentication-complete', self._authentication_complete_cb) self.cb_three = self.greeter.connect('show-message', self._auth_error_cb) self.greeter.connect_sync() return (self.cb_one, self.cb_two, self.cb_three) def _send_password_cb(self, _greeter, text, prompt_type): logger.debug('Need to show prompt: {}'.format(text)) if _greeter.get_in_authentication(): logger.debug('Sending password to LightDM') _greeter.respond(self.unix_password) def _authentication_complete_cb(self, _greeter): logger.debug('Authentication process is complete') if not _greeter.get_is_authenticated(): logger.warn('Could not authenticate user {}'.format(self.unix_username)) self._auth_error_cb(_('Incorrect password (The default is "kano")')) return logger.info( 'The user {} is authenticated. Starting LightDM X Session' .format(self.unix_username)) set_last_user(self.unix_username) if not _greeter.start_session_sync('lightdm-xsession'): logger.error('Failed to start session') else: logger.info('Login failed') def _auth_error_cb(self, text, message_type=None): logger.info('There was an error logging in: {}'.format(text)) win = self.get_toplevel() win.go_to_users() self.login_btn.stop_spinner() self.login_btn.set_sensitive(True) self.newuser_btn.set_sensitive(True) error = KanoDialog(title_text=_('Error Synchronizing account'), description_text=text, parent_window=self.get_toplevel()) error.dialog.set_position(Gtk.WindowPosition.CENTER_ALWAYS) error.run() def _error_message_box(self, title, description): ''' Show a standard error message box ''' self.login_btn.stop_spinner() self.login_btn.set_sensitive(True) errormsg = KanoDialog(title_text=title, description_text=description, button_dict=[ { 'label': _('OK').upper(), 'color': 'red', 'return_value': True }]) errormsg.dialog.set_position(Gtk.WindowPosition.CENTER_ALWAYS) errormsg.run() # Clean up password field self.password.set_text('') return def grab_focus(self): ''' Clear username and password previous text, and gain focus. ''' self.username.set_text('') self.password.set_text('')
class ResetPassword(Gtk.Box): def __init__(self, win): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.win = win self.win.set_decorated(False) self.win.set_main_widget(self) self.heading = Heading( _("Reset your password"), _("We'll send a new password to your email") ) self.pack_start(self.heading.container, False, False, 10) self.labelled_entries = LabelledEntries([ {"heading": _("Email"), "subheading": ""} ]) align = Gtk.Alignment(xscale=0, xalign=0.5) self.pack_start(align, False, False, 15) self.labelled_entries.set(0, 0, 1, 1) self.labelled_entries.set_hexpand(True) align.add(self.labelled_entries) # Read email from file user_email = get_email() self.email_entry = self.labelled_entries.get_entry(0) self.email_entry.set_text(user_email) self.email_entry.connect("key-release-event", self.activate) self.button = KanoButton(_("Reset password").upper()) self.button.pack_and_align() self.button.connect("button-release-event", self.activate) self.button.connect("key-release-event", self.activate) self.button.set_padding(30, 30, 0, 0) self.pack_start(self.button.align, False, False, 0) self.win.show_all() def activate(self, widget, event): if not hasattr(event, 'keyval') or event.keyval == 65293: watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) self.win.get_window().set_cursor(watch_cursor) self.button.set_sensitive(False) self.button.start_spinner() thread = threading.Thread(target=self.send_new_password) thread.start() def send_new_password(self): # User may change email email = self.labelled_entries.get_entry(0).get_text() success, text = reset_password(email) if success: title = _("Success!") description = _("Sent new password to your email") button_dict = { _("Go to login screen").upper(): {"return_value": 12}, _("Quit").upper(): {"return_value": 10, "color": "red"} } else: title = _("Something went wrong!") description = text button_dict = { _("Quit").upper(): {"return_value": 10, "color": "red"}, _("Try again").upper(): {"return_value": 11} } GObject.idle_add( self.finished_thread_cb, title, description, button_dict ) def finished_thread_cb(self, title, description, button_dict): kdialog = KanoDialog( title, description, button_dict=button_dict, parent_window=self.win ) response = kdialog.run() self.win.get_window().set_cursor(None) self.button.stop_spinner() self.button.set_sensitive(True) if response == 10: Gtk.main_quit() # stay put elif response == 11: pass elif response == 12: self.go_to_login_screen() def go_to_login_screen(self): self.win.remove_main_widget() Login(self.win)
class EditableList(Gtk.Grid): def __init__(self, size_x=400, size_y=150): Gtk.Grid.__init__(self) self.set_row_spacing(10) self.set_column_spacing(10) scroll = ScrolledWindow() scroll.set_size_request(size_x, size_y) scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.edit_list_store = Gtk.ListStore(str) self.edit_list = Gtk.TreeView(self.edit_list_store) self.edit_list.set_headers_visible(False) renderer = Gtk.CellRendererText() renderer.set_property('editable', True) renderer.connect('edited', self._item_edited_handler) renderer.connect('editing-started', self._item_edit_started) renderer.connect('editing-canceled', self._item_edit_canceled) column = Gtk.TreeViewColumn(cell_renderer=renderer, text=0) self.edit_list.append_column(column) self._add_btn = KanoButton(_("ADD")) self._add_btn.connect('button-release-event', self.add) self._rm_btn = KanoButton(_("REMOVE")) self._set_rm_btn_state() self._rm_btn.connect('button-release-event', self.rm) scroll.add_with_viewport(self.edit_list) self.attach(scroll, 0, 0, 2, 1) self.attach(self._add_btn, 0, 1, 1, 1) self.attach(self._rm_btn, 1, 1, 1, 1) def __contains__(self, item): return item in [row[0] for row in self.edit_list_store] def add(self, button, event): self.edit_list_store.append(['']) self.edit_list.grab_focus() row = len(self.edit_list_store) - 1 col = self.edit_list.get_column(0) self.edit_list.set_cursor(row, col, start_editing=True) self._rm_btn.set_sensitive(False) def rm(self, button=None, event=None): selection = self.edit_list.get_selection() dummy, selected = selection.get_selected() if not selected: return self.edit_list_store.remove(selected) self._set_rm_btn_state() def _item_edited_handler(self, cellrenderertext, path, new_text): if new_text is None: # FIXME: the reason for the os.system here is that the 'edited' signal # triggers on a key-pressed-event and the dialog closes on release. So # you would only see the dialog while holding down the 'ENTER' key. title = _("Invalid website given") description = _("\nWe need to make sure the website URL is valid.\n" \ "Please enter the full URL as it appears in your browser.\n\n" \ "Example: http://www.google.com\n") buttons = _("OK:red:1") cmd = 'kano-dialog title="{}" description="{}" buttons="{}" no-taskbar &'.format( title.encode('utf8'), description.encode('utf8'), buttons.encode('utf8')) os.system(cmd) self.rm() else: selection = self.edit_list.get_selection() dummy, selected = selection.get_selected() if new_text and new_text not in self: self.edit_list_store.set_value(selected, 0, new_text) else: row = self.edit_list_store[selected] old_text = row[0] if not old_text: self.rm() self._add_btn.set_sensitive(True) self._set_rm_btn_state() def _item_edit_started(self, *_): self._add_btn.set_sensitive(False) def _item_edit_canceled(self, *_): self._add_btn.set_sensitive(True) self.rm() def _set_rm_btn_state(self): state = len(self.edit_list_store) != 0 self._rm_btn.set_sensitive(state)
class RegistrationScreen(Gtk.Box): """ """ def __init__(self, win): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.win = win self.win.set_main_widget(self) self.win.set_decorated(True) title = Heading(_("Kano World"), _("Choose a cool name and secure password")) self.pack_start(title.container, False, False, 0) self.data_screen = GetData() # TODO: refactor this self.data_screen.connect('widgets-filled', self._enable_register_button) self.data_screen.connect('widgets-empty', self._disable_register_button) self.add(self.data_screen) self.register_button = KanoButton(_("JOIN KANO WORLD")) self.register_button.set_sensitive(False) self.register_button.set_margin_top(10) self.register_button.set_margin_left(30) self.register_button.set_margin_right(30) self.register_button.set_margin_bottom(30) self.register_button.connect('clicked', self._on_register_button) self.pack_end(self.register_button, False, False, 0) self.win.show_all() def _enable_register_button(self, widget=None): """ """ self.register_button.set_sensitive(True) def _disable_register_button(self, widget=None): """ """ self.register_button.set_sensitive(False) def _on_register_button(self, widget=None): # TODO: refactor this """ """ if not is_internet(): self._show_not_internet_dialog() return # Get the username, password and birthday data = self.data_screen.get_widget_data() email = data['email'] username = data['username'] # Validate that the email address format is correct email_error = validate_email(email) if email_error: self._show_error_dialog(_("Incorrect Email address"), email_error) return if not self._is_username_available(username): self._show_username_taken_dialog(username) return # We can save the username to kano-profile # Don't save password as this is private self.data_screen.save_username_and_birthday() # TODO: rename this self.data_screen.cache_emails() data = self.data_screen.get_widget_data() # This means no threads are needed. while Gtk.events_pending(): # TODO: why is this needed? Gtk.main_iteration() # Try and register the account on the server password = data['password'] success, text = register_(email, username, password, marketing_enabled=True) # This should no longer be needed, since this is checked in the first # screen. However there is a small chance someone could take the # username while the user is in the process of registering if not success: if text.strip() == _("Cannot register, problem: " "Username already registered"): self._show_username_taken_dialog(username) else: logger.info("problem with registration: {}".format(text)) return_value = 'FAIL' self._create_dialog(title=_("Houston, we have a problem"), description=str(text)) track_data('world-registration-failed', {'reason': text}) else: logger.info("registration successful") # saving hardware info and initial Kano version save_hardware_info() save_kano_version() # running kano-sync after registration logger.info("running kano-sync after successful registration") cmd = '{bin_dir}/kano-sync --sync -s'.format(bin_dir=bin_dir) run_bg(cmd) return_value = 'SUCCEED' self._create_dialog( title=_("Profile activated!"), description=_("Now you can share stuff, build your character, " "and connect with friends.")) self.win.get_window().set_cursor(None) # Close the app if it was successful if return_value == 'SUCCEED': Gtk.main_quit() def _is_username_available(self, name): """ Returns True if username is available, and False otherwise """ # Use the endpoint api.kano.me/users/username/:name success, text, data = request_wrapper( 'get', '/users/username/{}'.format(name), headers=content_type_json) if not success and text.strip() == "User not found": return True elif success: # Username is definitely taken return False else: # Maybe let the user know something went wrong? e.g. if there's no # internet, launch a dialog at this point return False def _create_dialog(self, title, description): # TODO: refactor this kdialog = KanoDialog(title, description, parent_window=self.win) rv = kdialog.run() return rv def _show_error_dialog(self, title, description): # TODO: refactor this kdialog = KanoDialog(title, description, parent_window=self.win) kdialog.run() def _show_username_taken_dialog(self, username): # TODO: refactor this track_data('world-registration-username-taken', {'username': username}) kd = KanoDialog(_("This username is taken!"), _("Try another one"), parent_window=self.win) kd.run() self.data_screen.username.set_text("") self.data_screen.validate_username() self._disable_register_button() self.data_screen.username.grab_focus() def _show_not_internet_dialog(self): # TODO: refactor this kd = KanoDialog(_("You don't have internet"), _("Do you want to connect to WiFi?"), [{ 'label': _("YES"), 'color': 'green', 'return_value': 0 }, { 'label': _("NO"), 'color': 'red', 'return_value': 1 }], parent_window=self.win) response = kd.run() # Close the dialog while Gtk.events_pending(): # TODO: why is this needed? Gtk.main_iteration() if response == 0: subprocess.Popen("sudo kano-wifi-gui", shell=True)
class RegistrationScreen(Gtk.Box): """ """ def __init__(self, win): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.win = win self.win.set_main_widget(self) self.win.set_decorated(True) title = Heading( _("Kano World"), _("Choose a cool name and secure password") ) self.pack_start(title.container, False, False, 0) self.data_screen = GetData() # TODO: refactor this self.data_screen.connect('widgets-filled', self._enable_register_button) self.data_screen.connect('widgets-empty', self._disable_register_button) self.add(self.data_screen) self.register_button = KanoButton(_("JOIN KANO WORLD")) self.register_button.set_sensitive(False) self.register_button.set_margin_top(10) self.register_button.set_margin_left(30) self.register_button.set_margin_right(30) self.register_button.set_margin_bottom(30) self.register_button.connect('clicked', self._on_register_button) self.pack_end(self.register_button, False, False, 0) self.win.show_all() def _enable_register_button(self, widget=None): """ """ self.register_button.set_sensitive(True) def _disable_register_button(self, widget=None): """ """ self.register_button.set_sensitive(False) def _on_register_button(self, widget=None): # TODO: refactor this """ """ if not is_internet(): self._show_not_internet_dialog() return # Get the username, password and birthday data = self.data_screen.get_widget_data() email = data['email'] username = data['username'] # Validate that the email address format is correct email_error = validate_email(email) if email_error: self._show_error_dialog(_("Incorrect Email address"), email_error) return if not self._is_username_available(username): self._show_username_taken_dialog(username) return # We can save the username to kano-profile # Don't save password as this is private self.data_screen.save_username_and_birthday() # TODO: rename this self.data_screen.cache_emails() data = self.data_screen.get_widget_data() # This means no threads are needed. while Gtk.events_pending(): # TODO: why is this needed? Gtk.main_iteration() # Try and register the account on the server password = data['password'] success, text = register_(email, username, password, marketing_enabled=True) # This should no longer be needed, since this is checked in the first # screen. However there is a small chance someone could take the # username while the user is in the process of registering if not success: if text.strip() == _("Cannot register, problem: " "Username already registered"): self._show_username_taken_dialog(username) else: logger.info("problem with registration: {}".format(text)) return_value = 'FAIL' self._create_dialog( title=_("Houston, we have a problem"), description=str(text) ) track_data('world-registration-failed', {'reason': text}) else: logger.info("registration successful") # saving hardware info and initial Kano version save_hardware_info() save_kano_version() # running kano-sync after registration logger.info("running kano-sync after successful registration") cmd = '{bin_dir}/kano-sync --sync -s'.format(bin_dir=bin_dir) run_bg(cmd) return_value = 'SUCCEED' self._create_dialog( title=_("Profile activated!"), description=_("Now you can share stuff, build your character, " "and connect with friends.") ) self.win.get_window().set_cursor(None) # Close the app if it was successful if return_value == 'SUCCEED': Gtk.main_quit() def _is_username_available(self, name): """ Returns True if username is available, and False otherwise """ # Use the endpoint api.kano.me/users/username/:name success, text, data = request_wrapper( 'get', '/users/username/{}'.format(name), headers=content_type_json ) if not success and text.strip() == "User not found": return True elif success: # Username is definitely taken return False else: # Maybe let the user know something went wrong? e.g. if there's no # internet, launch a dialog at this point return False def _create_dialog(self, title, description): # TODO: refactor this kdialog = KanoDialog( title, description, parent_window=self.win ) rv = kdialog.run() return rv def _show_error_dialog(self, title, description): # TODO: refactor this kdialog = KanoDialog( title, description, parent_window=self.win ) kdialog.run() def _show_username_taken_dialog(self, username): # TODO: refactor this track_data('world-registration-username-taken', {'username': username}) kd = KanoDialog( _("This username is taken!"), _("Try another one"), parent_window=self.win ) kd.run() self.data_screen.username.set_text("") self.data_screen.validate_username() self._disable_register_button() self.data_screen.username.grab_focus() def _show_not_internet_dialog(self): # TODO: refactor this kd = KanoDialog( _("You don't have internet"), _("Do you want to connect to WiFi?"), [ { 'label': _("YES"), 'color': 'green', 'return_value': 0 }, { 'label': _("NO"), 'color': 'red', 'return_value': 1 } ], parent_window=self.win ) response = kd.run() # Close the dialog while Gtk.events_pending(): # TODO: why is this needed? Gtk.main_iteration() if response == 0: subprocess.Popen("sudo kano-wifi-gui", shell=True)
class FeedbackWindow(MainWindow): CLOSE_FEEDBACK = 0 KEEP_OPEN = 1 LAUNCH_WIFI = 2 WIDTH = 400 def __init__(self, bug_report=False): ''' Initialises the window, creating a report or contact window ''' MainWindow.__init__(self, subject='Kano Desktop Feedback Widget') self.bug_report = bug_report if self.bug_report: self.report_window() else: self.contact_window() def contact_window(self): ''' Contact Us window Contains text view and a Send button ''' # delete the directory containing all the info we'll send, and recreate delete_tmp_dir() create_tmp_dir() ApplicationWindow.__init__( self, _('Contact Us'), # noqa: F821 self.WIDTH, 0.35 ) screen = Gdk.Screen.get_default() specific_provider = Gtk.CssProvider() specific_provider.load_from_path(Media.media_dir() + 'css/style.css') style_context = Gtk.StyleContext() style_context.add_provider_for_screen(screen, specific_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) # Make sure this window has no icon in the task bar # so it plays nice with kdesk-blur self.set_property('skip-taskbar-hint', True) self._grid = Gtk.Grid() # Create top bar self._top_bar = TopBar( title=_("Contact Us"), # noqa: F821 window_width=self.WIDTH, has_buttons=False ) self._top_bar.set_close_callback(Gtk.main_quit) self.set_decorated(True) self.set_titlebar(self._top_bar) # Create Text view self._text = Gtk.TextView() self._text.set_editable(True) self._text.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self._text.set_size_request(self.WIDTH, -1) self._textbuffer = self._text.get_buffer() self._textbuffer.set_text( _("Type your feedback here!") # noqa: F821 ) self._clear_buffer_handler_id = self._textbuffer.connect( "insert-text", self.clear_buffer ) scrolledwindow = ScrolledWindow() scrolledwindow.set_vexpand(True) scrolledwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolledwindow.apply_styling_to_widget() scrolledwindow.add(self._text) scrolledwindow.set_margin_left(2) scrolledwindow.set_margin_right(2) scrolledwindow.set_margin_top(2) scrolledwindow.set_margin_bottom(2) # Very hacky way to get a border: create a grey event box # which is a little bigger than the widget below border = Gtk.EventBox() border.get_style_context().add_class("grey") border.add(scrolledwindow) self._grid.attach(border, 0, 0, 1, 1) border.set_margin_left(20) border.set_margin_right(20) border.set_margin_top(10) border.set_margin_bottom(20) # Create send button self._send_button = KanoButton( _("SEND") # noqa: F821 ) self._send_button.set_sensitive(False) self._send_button.connect("button_press_event", self.send_feedback) self._send_button.pack_and_align() self._send_button.align.set_padding(10, 10, 0, 0) bottom_background = Gtk.EventBox() bottom_background.get_style_context().add_class("grey") bottom_background.add(self._send_button.align) self._grid.attach(bottom_background, 0, 1, 1, 1) self._grid.set_row_spacing(0) self.set_main_widget(self._grid) # kano-profile stat collection try: from kano_profile.badges import \ increment_app_state_variable_with_dialog increment_app_state_variable_with_dialog( 'kano-feedback', 'starts', 1 ) except Exception: pass def report_window(self): ''' Report window Contains 2 text views and Take Screenshot, Add Image and Send buttons ''' ApplicationWindow.__init__( self, _('Report a Problem'), # noqa: F821 self.WIDTH, 0.35 ) screen = Gdk.Screen.get_default() specific_provider = Gtk.CssProvider() specific_provider.load_from_path(Media.media_dir() + 'css/style.css') style_context = Gtk.StyleContext() style_context.add_provider_for_screen(screen, specific_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) self.set_icon_name("feedback") self._grid = Gtk.Grid() # Create top bar self._top_bar = TopBar( title=_("Report a Problem"), # noqa: F821 window_width=self.WIDTH, has_buttons=False ) self._top_bar.set_close_callback(Gtk.main_quit) self.set_decorated(True) self.set_titlebar(self._top_bar) self.entry = Gtk.Entry() self.entry.props.placeholder_text = \ _("Add subject (optional)") # noqa: F821 self.entry.set_margin_left(20) self.entry.set_margin_right(20) self.entry.set_margin_top(20) self.entry.set_margin_bottom(10) self._grid.attach(self.entry, 0, 0, 1, 1) # Create Text view self._text = Gtk.TextView() self._text.set_editable(True) self._text.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self._text.set_size_request(self.WIDTH, -1) self._textbuffer = self._text.get_buffer() self._textbuffer.set_text( _("Type your problem here!") # noqa: F821 ) self._clear_buffer_handler_id = self._textbuffer.connect( "insert-text", self.clear_buffer ) scrolledwindow = ScrolledWindow() scrolledwindow.set_vexpand(True) scrolledwindow.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC ) scrolledwindow.apply_styling_to_widget() scrolledwindow.add(self._text) scrolledwindow.set_margin_left(2) scrolledwindow.set_margin_right(2) scrolledwindow.set_margin_top(2) scrolledwindow.set_margin_bottom(2) # Very hacky way to get a border: create a grey event box # which is a little bigger than the widget below border = Gtk.EventBox() border.get_style_context().add_class("grey") border.add(scrolledwindow) self._grid.attach(border, 0, 1, 1, 1) border.set_margin_left(20) border.set_margin_right(20) border.set_margin_top(10) border.set_margin_bottom(20) # Create take screenshot button self._screenshot_button = KanoButton( _("TAKE SCREENSHOT"), # noqa: F821 "blue" ) self._screenshot_button.set_sensitive(True) self._screenshot_button.connect("button_press_event", self.screenshot_clicked) # Create attach screenshot button self._attach_button = KanoButton( _("ADD IMAGE"), # noqa: F821 "blue" ) self._attach_button.set_sensitive(True) self._attach_button.connect("button_press_event", self.attach_clicked) # Create send button self._send_button = KanoButton( _("SEND") # noqa: F821 ) self._send_button.set_sensitive(False) self._send_button.connect("button_press_event", self.send_feedback) self._send_button.pack_and_align() self._send_button.set_margin(10, 0, 10, 0) self.screenshot_box = Gtk.ButtonBox() self.screenshot_box.set_layout(Gtk.ButtonBoxStyle.CENTER) self.screenshot_box.set_spacing(20) self.pack_screenshot_buttons() self.screenshot_box.set_margin_bottom(20) self._grid.attach(self.screenshot_box, 0, 2, 1, 1) # Create grey box to put the button in self.bottom_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.bottom_box.pack_start(self._send_button.align, False, False, 0) bottom_background = Gtk.EventBox() bottom_background.get_style_context().add_class("grey") bottom_background.add(self.bottom_box) self._grid.attach(bottom_background, 0, 3, 1, 1) self._grid.set_row_spacing(0) self.set_main_widget(self._grid) # kano-profile stat collection try: from kano_profile.badges import increment_app_state_variable_with_dialog increment_app_state_variable_with_dialog( 'kano-feedback', 'starts', 1 ) except Exception: pass def screenshot_clicked(self, button=None, event=None): ''' Takes a screenshot while minimising the window ''' # minimise the window self.iconify() take_screenshot() self.include_screenshot() # restore the window self.deiconify() def attach_clicked(self, button=None, event=None): ''' Opens the File Chooser Dialog. If image selected then copy it to the feedback folder ''' screenshot = None # Open file manager dialog = Gtk.FileChooserDialog( _("Please choose a file"), # noqa: F821 self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK) ) self.add_filters(dialog) response = dialog.run() if response == Gtk.ResponseType.OK: screenshot = dialog.get_filename() dialog.destroy() # Copy image file into feedback folder if screenshot is not None: copy_screenshot(screenshot) self.include_screenshot() def add_filters(self, dialog): ''' Add image type filters Used for the File Chooser Dialog ''' # Image filter filter_images = Gtk.FileFilter() filter_images.set_name("Images") filter_images.add_mime_type("image/png") filter_images.add_mime_type("image/jpeg") filter_images.add_mime_type("image/gif") filter_images.add_pattern("*.png") filter_images.add_pattern("*.jpg") filter_images.add_pattern("*.gif") filter_images.add_pattern("*.tif") filter_images.add_pattern("*.xpm") dialog.add_filter(filter_images) # Any file filter filter_any = Gtk.FileFilter() filter_any.set_name("Any files") filter_any.add_pattern("*") dialog.add_filter(filter_any) def include_screenshot(self): ''' This is the box containing the filename of the screenshot, and the option to display it or remove it ''' if not hasattr(self, "screenshot"): # We pack the buttons into an event box with styling # so that we can have a container with a border radius # without having ugly gaps between the buttons. self.screenshot = Gtk.EventBox() self.screenshot.get_style_context().add_class("kano_button") self.screenshot.get_style_context().add_class("blue_background") remove_screenshot = Gtk.Button() attach_cursor_events(remove_screenshot) remove_icon = Gtk.Image.new_from_file("/usr/share/kano-feedback/media/icons/close.png") remove_screenshot.add(remove_icon) remove_screenshot.connect("button-release-event", self.remove_screenshot) remove_screenshot.get_style_context().add_class("blue_background") show_screenshot = Gtk.Button() attach_cursor_events(show_screenshot) show_icon = Gtk.Image() show_icon.set_from_file("/usr/share/kano-feedback/media/icons/preview.png") show_screenshot.add(show_icon) show_screenshot.connect("button-release-event", self.show_screenshot) show_screenshot.get_style_context().add_class("blue_background") label = Gtk.Label(SCREENSHOT_NAME.upper()) label.set_padding(10, 0) box = Gtk.Box() box.pack_start(label, False, False, 0) box.pack_end(remove_screenshot, False, False, 0) box.pack_end(show_screenshot, False, False, 0) self.screenshot.add(box) self.screenshot_box.remove(self._screenshot_button) self.screenshot_box.remove(self._attach_button) self.screenshot_box.pack_start(self.screenshot, False, False, 0) def remove_screenshot(self, widget, event): ''' Remove screenshot button action ''' delete_screenshot() self.screenshot_box.remove(self.screenshot) self.pack_screenshot_buttons() self.show_all() def show_screenshot(self, widget, event): ''' Creates and displays a dialog with the screenshot image ''' height = Gdk.Screen().get_default().get_height() width = Gdk.Screen().get_default().get_width() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(SCREENSHOT_PATH, width * 0.5, height * 0.5) image = Gtk.Image.new_from_pixbuf(pixbuf) dialog = KanoDialog( _("Screenshot"), # noqa: F821 widget=image ) dialog.run() def pack_screenshot_buttons(self): ''' Pack the screenshot buttons into a box ''' self.screenshot_box.pack_start(self._screenshot_button, False, False, 0) self.screenshot_box.set_child_non_homogeneous(self._screenshot_button, True) self.screenshot_box.pack_start(self._attach_button, False, False, 0) self.screenshot_box.set_child_non_homogeneous(self._attach_button, True)
class PasswordScreen(Gtk.Box): def __init__( self, win, wiface, network_name, encryption, wrong_password=False ): ''' Show the screen with the option of adding a password and connecting to a network ''' Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self._win = win self._win.set_main_widget(self) self._win.top_bar.enable_prev() self._wiface = wiface self._network_name = network_name self._encryption = encryption # Keep track if the user has already entered the wrong password before # so that we only pack the "password incorrect" label once self._wrong_password_used_before = False self._heading = Heading( "Connect to the network", self._network_name, self._win.is_plug(), True ) self._heading.set_prev_callback(self._refresh_networks) self._heading.container.set_margin_right(20) self._heading.container.set_margin_left(20) if wrong_password: image_path = os.path.join(img_dir, "password-fail.png") wrong_password = self._create_wrong_password_label() self._heading.container.pack_start(wrong_password, True, True, 0) else: image_path = os.path.join(img_dir, "password.png") self._padlock_image = Gtk.Image.new_from_file(image_path) self._password_entry = Gtk.Entry() self._password_entry.set_placeholder_text("Password") self._password_entry.set_visibility(False) self._password_entry.get_style_context().add_class("password_entry") self._password_entry.set_margin_left(60) self._password_entry.set_margin_right(60) self._password_entry.connect("key-release-event", self._set_button_sensitive) # If Enter key is pressed on the password entry, we want to act as # though the connect_btn was clicked self._password_entry.connect( "key-release-event", self._on_connect_key_wrapper ) self._connect_btn = KanoButton("CONNECT") self._connect_btn.connect('clicked', self._on_connect) self._connect_btn.set_sensitive(False) self._connect_btn.set_margin_right(100) self._connect_btn.set_margin_left(100) self._connect_btn.pack_and_align() self._show_password = Gtk.CheckButton.new_with_label("Show password") self._show_password.get_style_context().add_class("show_password") self._show_password.connect("toggled", self._change_password_entry_visiblity) self._show_password.set_active(True) self._show_password.set_margin_left(100) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.add(vbox) vbox.pack_start(self._heading.container, False, False, 10) vbox.pack_start(self._padlock_image, False, False, 10) vbox.pack_start(self._password_entry, False, False, 10) vbox.pack_start(self._show_password, False, False, 10) vbox.pack_end(self._connect_btn.align, False, False, 40) # Entry should have the keyboard focus self._password_entry.grab_focus() self.show_all() def _create_wrong_password_label(self): label = Gtk.Label("Password incorrect") label.get_style_context().add_class("wrong_password_label") return label def _change_password_entry_visiblity(self, widget): ''' Depending on the checkbox, change the writing in the password entry to be readable. ''' visibility = self._show_password.get_active() self._password_entry.set_visibility(visibility) def _refresh_networks(self, widget=None): from kano_wifi_gui.RefreshNetworks import RefreshNetworks RefreshNetworks(self._win) def _on_connect_key_wrapper(self, widget, event): if event.keyval == Gdk.KEY_Return: self._on_connect() def _on_connect(self, widget=None): passphrase = self._password_entry.get_text() ConnectToNetwork( self._win, self._network_name, passphrase, self._encryption ) def _set_button_sensitive(self, widget, event): self._connect_btn.set_sensitive(True) def _thread_finish(self, success): if success: self._success_screen() else: self._wrong_password_screen() def _success_screen(self): self._win.remove_main_widget() title = "Success!" description = "You're connected" buttons = [ { "label": "OK", "color": "green", "type": "KanoButton", "callback": Gtk.main_quit } ] img_path = os.path.join(img_dir, "internet.png") self._win.set_main_widget( Template( title, description, buttons, self._win.is_plug(), img_path ) ) def _disable_widgets_start_spinner(self): self._connect_btn.start_spinner() self._connect_btn.set_sensitive(False) self._win.top_bar.prev_button.set_sensitive(False) self._password_entry.set_sensitive(False) self._show_password.set_sensitive(False) def _enable_widgets_stop_spinner(self): self._connect_btn.stop_spinner() self._connect_btn.set_sensitive(True) self._win.top_bar.prev_button.set_sensitive(True) self._password_entry.set_sensitive(True) self._show_password.set_sensitive(True)
class RecoverUsername(Gtk.Box): def __init__(self, win): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.win = win self.win.set_decorated(False) self.win.set_main_widget(self) self.heading = Heading(_("Forgotten your username"), _("We'll send a reminder to your email")) self.pack_start(self.heading.container, False, False, 10) self.labelled_entries = LabelledEntries([{ 'heading': _("Email"), 'subheading': "" }]) align = Gtk.Alignment(xscale=0, xalign=0.5) self.pack_start(align, False, False, 15) self.labelled_entries.set(0, 0, 1, 1) self.labelled_entries.set_hexpand(True) align.add(self.labelled_entries) self.email_entry = self.labelled_entries.get_entry(0) self.email_entry.set_text("") self.email_entry.connect('key-release-event', self.activate) self.button = KanoButton(_("REQUEST REMINDER")) self.button.pack_and_align() self.button.connect('button-release-event', self.activate) self.button.connect('key-release-event', self.activate) self.button.set_padding(30, 30, 0, 0) self.pack_start(self.button.align, False, False, 0) self.email_entry.grab_focus() self.win.show_all() def activate(self, widget, event): if not hasattr(event, 'keyval') or event.keyval == 65293: watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) self.win.get_window().set_cursor(watch_cursor) self.button.set_sensitive(False) self.button.start_spinner() thread = threading.Thread(target=self.send_new_password) thread.start() def send_new_password(self): # User may change email email = self.labelled_entries.get_entry(0).get_text() success, text = recover_username(email) if success: title = _("Success!") description = _("Sent a reminder to your email") button_dict = { _("GO TO LOGIN SCREEN"): { 'return_value': 12 }, _("QUIT"): { 'return_value': 10, 'color': 'red' } } else: title = _("Something went wrong!") description = text button_dict = { _("QUIT"): { 'return_value': 10, 'color': 'red' }, _("TRY AGAIN"): { 'return_value': 11 } } GObject.idle_add(self.finished_thread_cb, title, description, button_dict) def finished_thread_cb(self, title, description, button_dict): kdialog = KanoDialog(title, description, button_dict=button_dict, parent_window=self.win) response = kdialog.run() self.win.get_window().set_cursor(None) self.button.stop_spinner() self.button.set_sensitive(True) if response == 10: Gtk.main_quit() # stay put elif response == 11: pass elif response == 12: self.go_to_login_screen() def go_to_login_screen(self): self.win.remove_main_widget() Login(self.win)
class EditableList(Gtk.Grid): def __init__(self, size_x=400, size_y=150): Gtk.Grid.__init__(self) self.set_row_spacing(10) self.set_column_spacing(10) scroll = ScrolledWindow() scroll.set_size_request(size_x, size_y) scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.edit_list_store = Gtk.ListStore(str) self.edit_list = Gtk.TreeView(self.edit_list_store) self.edit_list.set_headers_visible(False) renderer = Gtk.CellRendererText() renderer.set_property('editable', True) renderer.connect('edited', self._item_edited_handler) renderer.connect('editing-started', self._item_edit_started) renderer.connect('editing-canceled', self._item_edit_canceled) column = Gtk.TreeViewColumn(cell_renderer=renderer, text=0) self.edit_list.append_column(column) self._add_btn = KanoButton(_("ADD")) self._add_btn.connect('button-release-event', self.add) self._rm_btn = KanoButton(_("REMOVE")) self._set_rm_btn_state() self._rm_btn.connect('button-release-event', self.rm) scroll.add_with_viewport(self.edit_list) self.attach(scroll, 0, 0, 2, 1) self.attach(self._add_btn, 0, 1, 1, 1) self.attach(self._rm_btn, 1, 1, 1, 1) def __contains__(self, item): return item in [row[0] for row in self.edit_list_store] def add(self, button, event): self.edit_list_store.append(['']) self.edit_list.grab_focus() row = len(self.edit_list_store) - 1 col = self.edit_list.get_column(0) self.edit_list.set_cursor(row, col, start_editing=True) self._rm_btn.set_sensitive(False) def rm(self, button=None, event=None): selection = self.edit_list.get_selection() dummy, selected = selection.get_selected() if not selected: return self.edit_list_store.remove(selected) self._set_rm_btn_state() def _item_edited_handler(self, cellrenderertext, path, new_text): if new_text is None: # FIXME: the reason for the os.system here is that the 'edited' signal # triggers on a key-pressed-event and the dialog closes on release. So # you would only see the dialog while holding down the 'ENTER' key. title = _("Invalid website given") description = _("\nWe need to make sure the website URL is valid.\n" \ "Please enter the full URL as it appears in your browser.\n\n" \ "Example: http://www.google.com\n") buttons = _("OK:red:1") cmd = 'kano-dialog title="{}" description="{}" buttons="{}" no-taskbar &'.format( title.encode('utf8'), description.encode('utf8'), buttons.encode('utf8')) os.system(cmd) self.rm() else: selection = self.edit_list.get_selection() dummy, selected = selection.get_selected() if new_text and new_text not in self: self.edit_list_store.set_value(selected, 0, new_text) else: row = self.edit_list_store[selected] old_text = row[0] if not old_text: self.rm() self._add_btn.set_sensitive(True) self._set_rm_btn_state() def _item_edit_started(self, *_): self._add_btn.set_sensitive(False) def _item_edit_canceled(self, *_): self._add_btn.set_sensitive(True) self.rm() def _set_rm_btn_state(self): state = len(self.edit_list_store) != 0 self._rm_btn.set_sensitive(state)
class NetworkScreen(Gtk.Box): def __init__(self, win, network_list): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self._win = win self._wiface = self._win.wiface # The network that the user selects self._selected_network = {} # Setting new window here self._win.set_main_widget(self) self._win.top_bar.disable_prev() box = self._create_main_box(network_list) self.add(box) self._win.show_all() def _create_main_box(self, network_list): '''Show the screen with the different WiFi networks ''' heading = Heading( _("Connect to WiFi"), _("Choose a network"), self._win.is_plug(), back_btn=False ) # This box is to pack everything in the window vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) # For now, pack the network into a scrolled window sw = ScrolledWindow() sw.apply_styling_to_widget() sw.set_size_request(-1, 215) sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self._network_box = self._create_network_box(network_list) sw.add(self._network_box) # Pack the scrolled window into an event box to give the illusion of a # border sw_border = self._add_border_to_widget(sw) sw_border.set_margin_right(30) sw_border.set_margin_left(30) sw_border.set_margin_bottom(20) sw_border.set_margin_top(10) # Then pack all the elements into the vbox vbox.pack_start(heading.container, False, False, 0) vbox.pack_start(sw_border, False, False, 0) # Pack in the refresh connect buttons button_box = self._create_refresh_connect_buttons() vbox.pack_end(button_box, False, False, 30) return vbox def _create_network_box(self, network_list): '''Create the box containing the list of networks ''' # Setting up the box in which the network elements are to be positioned network_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) # Are these used anywhere? self._network_btns = [] network_connection = is_connected(self._wiface) # The network connection network_name = network_connection[0] connected = network_connection[3] image_path = os.path.join(img_dir, "padlock.png") # If the network list is empty, display a message to show it's not # broken if not len(network_list): no_networks_label = Gtk.Label(_("No networks detected!")) no_networks_label.get_style_context().add_class('no_networks_label') no_networks_label.set_margin_top(80) network_box.pack_start(no_networks_label, False, False, 0) return network_box # Otherwise, pack the networks into the scrolled window for network in network_list: # Network selection must be able to receive events network_btn = Gtk.Button() # Needs a box packed into it for the label and possibly # an icon box = Gtk.Box() network_btn.add(box) network_btn.get_style_context().add_class('network_btn') attach_cursor_events(network_btn) # Box must contain label of the network name label = Gtk.Label(network['essid']) box.pack_start(label, False, False, 0) # If the network name of the button matches the last attempted # connection, and we're connected to the internet, then # put a tick next to the name. # TODO: Since connected shows if you're connected to internet # you can be connected to ethernet and thus be shown to be # connected to the wrong network. if network['essid'] == network_name and \ connected: tick = tick_icon() box.pack_start(tick, False, False, 0) network_btn.connect( 'clicked', self._select_network, network, network_connection ) # Add padlock to the items that require a password if network['encryption'] != 'off': padlock_image = Gtk.Image.new_from_file(image_path) box.pack_end(padlock_image, False, False, 0) # Pack into the GUI for the networks network_box.pack_start(network_btn, False, False, 0) self._network_btns.append(network_btn) return network_box def _add_border_to_widget(self, widget): '''Add a grey border to the widget that is entered as an argument. This is done by creating a grey event box and packing a white box with a margin in it. ''' white_foreground = Gtk.EventBox() white_foreground.get_style_context().add_class('white') white_foreground.set_margin_left(3) white_foreground.set_margin_bottom(3) white_foreground.set_margin_top(3) white_foreground.set_margin_right(3) # Pack the scrolled window into an event box to give the illusion of a # border grey_border = Gtk.EventBox() grey_border.get_style_context().add_class('grey') grey_border.add(white_foreground) white_foreground.add(widget) return grey_border def _create_refresh_connect_buttons(self): '''Create the buttons used for the refresh button and the to connect to a network, and pack them into a button box. Returns the button box. ''' self._connect_btn = KanoButton(_("CONNECT")) self._connect_btn.pack_and_align() self.connect_handler = self._connect_btn.connect( 'clicked', self._first_time_connect ) self._connect_btn.set_sensitive(False) self._refresh_btn = self._create_refresh_button() # For now, show both connect and refresh buttons buttonbox = Gtk.ButtonBox() buttonbox.set_layout(Gtk.ButtonBoxStyle.CENTER) buttonbox.set_spacing(10) buttonbox.pack_start(self._refresh_btn, False, False, 0) buttonbox.pack_start(self._connect_btn.align, False, False, 0) if self._win.is_plug(): self._skip_btn = WhiteButton(_("Skip")) buttonbox.pack_start(self._skip_btn, False, False, 0) self._skip_btn.connect('clicked', self.skip) else: blank_label = Gtk.Label("") buttonbox.pack_start(blank_label, False, False, 0) return buttonbox # Attached to a callback, hence the extra argument def skip(self, skip_btn=None): # Exit with an extreme exit code so the init-flow knows the user # pressed SKIP sys.exit(100) def _set_connect_btn_status(self, connect=True): self._connect_btn.disconnect(self.connect_handler) if connect: self.connect_handler = self._connect_btn.connect( 'clicked', self._first_time_connect ) self._connect_btn.set_color('green') self._connect_btn.set_label(_("CONNECT")) else: self.connect_handler = self._connect_btn.connect( 'clicked', self._launch_disconnect_thread ) self._connect_btn.set_color('red') self._connect_btn.set_label(_("DISCONNECT")) def _launch_disconnect_thread(self, widget=None): watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) self._win.get_window().set_cursor(watch_cursor) self._connect_btn.start_spinner() self._connect_btn.set_sensitive(False) # Force the spinner to show on the window. while Gtk.events_pending(): Gtk.main_iteration() t = threading.Thread(target=self._threaded_disconnect) t.start() def _disconnect_screen(self): self._win.remove_main_widget() title = _("Disconnect complete.") description = _("You're now offline") buttons = [ { 'label': _("CLOSE"), 'type': 'KanoButton', 'color': 'red', 'callback': Gtk.main_quit }, { 'label': _("CONNECT"), 'type': 'KanoButton', 'color': 'green', 'callback': self._go_to_spinner_screen } ] img_path = os.path.join(img_dir, "no-wifi.png") self._win.set_main_widget( Template( title, description, buttons, self._win.is_plug(), img_path ) ) def _threaded_disconnect(self): ''' This is needed so we can show a spinner while the user is disconnecting ''' disconnect(self._wiface) def done(): self._disconnect_screen() self._win.get_window().set_cursor(None) self._connect_btn.stop_spinner() self._connect_btn.set_sensitive(True) GObject.idle_add(done) def _create_refresh_button(self): '''Create the refresh button. This it quite involved as you have to pack an image into the button which need to change when the cursor hovers over it, and change the cursor to be a hand over it. ''' refresh_icon_filepath = os.path.join(img_dir, "refresh.png") refresh_icon = Gtk.Image.new_from_file(refresh_icon_filepath) refresh_btn = Gtk.Button() refresh_btn.get_style_context().add_class('refresh_btn') refresh_btn.set_image(refresh_icon) attach_cursor_events(refresh_btn) # These are here in case we want to change the icon on mouse over refresh_btn.connect('enter-notify-event', self._set_refresh_hover_icon) refresh_btn.connect('leave-notify-event', self._set_refresh_normal_icon) refresh_btn.connect('clicked', self._go_to_spinner_screen) return refresh_btn # This is linked to enter-notify-event, hence the extra arguments def _set_refresh_hover_icon(self, widget=None, event=None): '''Change the refresh button's icon to the hover icon. ''' selected_path = os.path.join(img_dir, "rescan-hover.png") image = Gtk.Image.new_from_file(selected_path) self._refresh_btn.set_image(image) # This is linked to leave-notify-event, hence the extra arguments def _set_refresh_normal_icon(self, widget=None, event=None): '''Change the refresh button's icon to the normal icon. ''' unselected_path = os.path.join(img_dir, "refresh.png") image = Gtk.Image.new_from_file(unselected_path) self._refresh_btn.set_image(image) def _first_time_connect(self, widget=None): '''Check the selected network. If a password is needed, take the user to the password screen. Otherwise, try and connect. ''' if self._selected_network['encryption'] == "off": essid = self._selected_network['essid'] encryption = 'off' passphrase = '' ConnectToNetwork(self._win, essid, passphrase, encryption) else: self._go_to_password_screen() def _go_to_spinner_screen(self, button=None, event=None): '''Loading networks and showing the spinner screen. ''' from kano_wifi_gui.RefreshNetworks import RefreshNetworks RefreshNetworks(self._win) def _go_to_password_screen(self): self._win.remove_main_widget() PasswordScreen(self._win, self._wiface, self._selected_network["essid"], self._selected_network["encryption"]) def _select_network(self, button, network, network_connection): for network_btn in self._network_btns: network_btn.get_style_context().remove_class('selected') network_name = network_connection[0] connected = network_connection[3] self._selected_network = network button.get_style_context().add_class('selected') # If we are already connected to this network, # offer option to disconnect. if network['essid'] == network_name and connected: self._set_connect_btn_status(connect=False) else: self._set_connect_btn_status(connect=True) self._connect_btn.set_sensitive(True) def _disable_widgets_start_spinner(self): self._connect_btn.start_spinner() self._disable_widgets() def _enable_widgets_stop_spinner(self): self._connect_btn.stop_spinner() self._enable_widgets() def _disable_widgets(self): self._set_sensitivity_of_buttons(False) def _enable_widgets(self): self._connect_btn.stop_spinner() self._set_sensitivity_of_buttons(True) def _set_sensitivity_of_buttons(self, sensitivity): self._connect_btn.set_sensitive(sensitivity) self._refresh_btn.set_sensitive(sensitivity) # Do we want to block this? Or just make sure the application doesn't # fall over afterwards if hasattr(self, '_skip_btn'): # Skip button should be defined self._skip_btn.set_sensitive(sensitivity)
class SetAccount(Gtk.Box): def __init__(self, win): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.win = win self.win.set_main_widget(self) self.win.top_bar.enable_prev() self.win.change_prev_callback(self.win.go_to_home) self.added_or_removed_account = False main_heading = Heading(_("System account settings"), _("Set your account")) self.pass_button = KanoButton(_("CHANGE PASSWORD")) self.pass_button.pack_and_align() self.pass_button.connect('button-release-event', self.go_to_password_screen) self.pass_button.connect('key-release-event', self.go_to_password_screen) self.add_button = KanoButton(_("ADD ACCOUNT")) self.add_button.set_size_request(200, 44) self.add_button.connect('button-release-event', self.add_account) self.add_button.connect('key-release-event', self.add_account) self.remove_button = KanoButton(_("REMOVE ACCOUNT"), color='red') self.remove_button.set_size_request(200, 44) self.remove_button.connect('button-release-event', self.remove_account_dialog) self.remove_button.connect('key-release-event', self.remove_account_dialog) button_container = Gtk.Box() button_container.pack_start(self.add_button, False, False, 10) button_container.pack_start(self.remove_button, False, False, 10) button_align = Gtk.Alignment(xscale=0, xalign=0.5) button_align.add(button_container) accounts_heading = Heading(_("Accounts"), _("Add or remove accounts")) # Check if we already scheduled an account add or remove # We import kano-init locally to avoid circular dependency # the packages. try: from kano_init.utils import is_any_task_scheduled if is_any_task_scheduled(): self.disable_buttons() except ImportError: self.disable_buttons() self.pack_start(main_heading.container, False, False, 0) self.pack_start(self.pass_button.align, False, False, 0) self.pack_start(accounts_heading.container, False, False, 0) self.pack_start(button_align, False, False, 0) self.win.show_all() def go_to_password_screen(self, widget, event): if not hasattr(event, 'keyval') or event.keyval == Gdk.KEY_Return: self.win.clear_win() SetPassword(self.win) # Gets executed when ADD button is clicked def add_account(self, widget=None, event=None): if not hasattr(event, 'keyval') or event.keyval == Gdk.KEY_Return: kdialog = None try: # add new user command add_user() except UserError as e: kdialog = kano_dialog.KanoDialog(_("Error creating new user"), str(e), parent_window=self.win) else: kdialog = kano_dialog.KanoDialog( _("Reboot the system"), _("A new account will be created next time you reboot."), parent_window=self.win) # Tell user to reboot to see changes common.need_reboot = True kdialog.run() self.disable_buttons() # Gets executed when REMOVE button is clicked def remove_account_dialog(self, widget=None, event=None): if not hasattr(event, 'keyval') or event.keyval == Gdk.KEY_Return: # Bring in message dialog box kdialog = kano_dialog.KanoDialog( _("Are you sure you want to delete the current user?"), _("You will lose all the data on this account!"), [{ 'label': _("CANCEL"), 'color': 'red', 'return_value': False }, { 'label': _("OK"), 'color': 'green', 'return_value': True }], parent_window=self.win) do_delete_user = kdialog.run() if do_delete_user: self.disable_buttons() try: delete_user() except UserError as e: kdialog = kano_dialog.KanoDialog(_("Error deleting user"), str(e), parent_window=self.win) return kdialog = kano_dialog.KanoDialog( _("To finish removing this account, you need to reboot"), _("Do you want to reboot?"), [{ 'label': _("LATER"), 'color': 'grey', 'return_value': False }, { 'label': _("REBOOT NOW"), 'color': 'orange', 'return_value': True }], parent_window=self.win) do_reboot_now = kdialog.run() if do_reboot_now: os.system("sudo systemctl reboot") # Disables both buttons and makes the temp 'flag' folder def disable_buttons(self): self.add_button.set_sensitive(False) self.remove_button.set_sensitive(False) self.added_or_removed_account = True
class FeedbackWindow(MainWindow): CLOSE_FEEDBACK = 0 KEEP_OPEN = 1 LAUNCH_WIFI = 2 WIDTH = 400 def __init__(self, bug_report=False): ''' Initialises the window, creating a report or contact window ''' MainWindow.__init__(self, subject='Kano Desktop Feedback Widget') self.kano_world_client = KanoWorld() self.bug_report = bug_report if self.bug_report: self.report_window() else: self.contact_window() def contact_window(self): ''' Contact Us window Contains text view and a Send button ''' # delete the directory containing all the info we'll send, and recreate delete_tmp_dir() create_tmp_dir() ApplicationWindow.__init__( self, _('Contact Us'), # noqa: F821 self.WIDTH, 0.35) screen = Gdk.Screen.get_default() specific_provider = Gtk.CssProvider() specific_provider.load_from_path(Media.media_dir() + 'css/style.css') style_context = Gtk.StyleContext() style_context.add_provider_for_screen(screen, specific_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) # Make sure this window has no icon in the task bar # so it plays nice with kdesk-blur self.set_property('skip-taskbar-hint', True) self._grid = Gtk.Grid() # Create top bar self._top_bar = TopBar( title=_("Contact Us"), # noqa: F821 window_width=self.WIDTH, has_buttons=False) self._top_bar.set_close_callback(Gtk.main_quit) self.set_decorated(True) self.set_titlebar(self._top_bar) # Create Text view self._text = Gtk.TextView() self._text.set_editable(True) self._text.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self._text.set_size_request(self.WIDTH, -1) self._text.set_sensitive(self._pii_allowed()) self._textbuffer = self._text.get_buffer() if self._pii_allowed(): self._textbuffer.set_text( _("Type your feedback here!")) # noqa: F821 else: self._textbuffer.set_text( _("Want to send us more information? Ask your parent to check their email." )) # noqa: F821 self._clear_buffer_handler_id = self._textbuffer.connect( "insert-text", self.clear_buffer) scrolledwindow = ScrolledWindow() scrolledwindow.set_vexpand(True) scrolledwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolledwindow.apply_styling_to_widget() scrolledwindow.add(self._text) scrolledwindow.set_margin_left(2) scrolledwindow.set_margin_right(2) scrolledwindow.set_margin_top(2) scrolledwindow.set_margin_bottom(2) # Very hacky way to get a border: create a grey event box # which is a little bigger than the widget below border = Gtk.EventBox() border.get_style_context().add_class("grey") border.add(scrolledwindow) self._grid.attach(border, 0, 0, 1, 1) border.set_margin_left(20) border.set_margin_right(20) border.set_margin_top(10) border.set_margin_bottom(20) # Create send button self._send_button = KanoButton(_("SEND") # noqa: F821 ) self._send_button.set_sensitive(not self._pii_allowed()) self._send_button.connect("button_press_event", self.send_feedback) self._send_button.pack_and_align() self._send_button.align.set_padding(10, 10, 0, 0) bottom_background = Gtk.EventBox() bottom_background.get_style_context().add_class("grey") bottom_background.add(self._send_button.align) self._grid.attach(bottom_background, 0, 1, 1, 1) self._grid.set_row_spacing(0) self.set_main_widget(self._grid) # kano-profile stat collection try: from kano_profile.badges import \ increment_app_state_variable_with_dialog increment_app_state_variable_with_dialog('kano-feedback', 'starts', 1) except Exception: pass def report_window(self): ''' Report window Contains 2 text views and Take Screenshot, Add Image and Send buttons ''' ApplicationWindow.__init__( self, _('Report a Problem'), # noqa: F821 self.WIDTH, 0.35) screen = Gdk.Screen.get_default() specific_provider = Gtk.CssProvider() specific_provider.load_from_path(Media.media_dir() + 'css/style.css') style_context = Gtk.StyleContext() style_context.add_provider_for_screen(screen, specific_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) self.set_icon_name("feedback") self._grid = Gtk.Grid() # Create top bar self._top_bar = TopBar( title=_("Report a Problem"), # noqa: F821 window_width=self.WIDTH, has_buttons=False) self._top_bar.set_close_callback(Gtk.main_quit) self.set_decorated(True) self.set_titlebar(self._top_bar) self.entry = Gtk.Entry() if self._pii_allowed(): self.entry.props.placeholder_text = _( "Add subject (optional)") # noqa: F821 else: self.entry.props.placeholder_text = _( "Want to send us more information?") # noqa: F821 self.entry.set_margin_left(20) self.entry.set_margin_right(20) self.entry.set_margin_top(20) self.entry.set_margin_bottom(10) self.entry.set_sensitive(self._pii_allowed()) self._grid.attach(self.entry, 0, 0, 1, 1) # Create Text view self._text = Gtk.TextView() self._text.set_editable(True) self._text.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self._text.set_size_request(self.WIDTH, -1) self._text.set_sensitive(self._pii_allowed()) self._textbuffer = self._text.get_buffer() if self._pii_allowed(): self._textbuffer.set_text( _("Type your problem here!")) # noqa: F821 else: self._textbuffer.set_text( _("Ask your parent to check their email.")) # noqa: F821 self._clear_buffer_handler_id = self._textbuffer.connect( "insert-text", self.clear_buffer) scrolledwindow = ScrolledWindow() scrolledwindow.set_vexpand(True) scrolledwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolledwindow.apply_styling_to_widget() scrolledwindow.add(self._text) scrolledwindow.set_margin_left(2) scrolledwindow.set_margin_right(2) scrolledwindow.set_margin_top(2) scrolledwindow.set_margin_bottom(2) # Very hacky way to get a border: create a grey event box # which is a little bigger than the widget below border = Gtk.EventBox() border.get_style_context().add_class("grey") border.add(scrolledwindow) self._grid.attach(border, 0, 1, 1, 1) border.set_margin_left(20) border.set_margin_right(20) border.set_margin_top(10) border.set_margin_bottom(20) # Create take screenshot button self._screenshot_button = KanoButton( _("TAKE SCREENSHOT"), # noqa: F821 "blue") self._screenshot_button.set_sensitive(self._pii_allowed()) self._screenshot_button.connect("button_press_event", self.screenshot_clicked) # Create attach screenshot button self._attach_button = KanoButton( _("ADD IMAGE"), # noqa: F821 "blue") self._attach_button.set_sensitive(self._pii_allowed()) self._attach_button.connect("button_press_event", self.attach_clicked) # Create send button self._send_button = KanoButton(_("SEND") # noqa: F821 ) self._send_button.set_sensitive(not self._pii_allowed()) self._send_button.connect("button_press_event", self.send_feedback) self._send_button.pack_and_align() self._send_button.set_margin(10, 0, 10, 0) self.screenshot_box = Gtk.ButtonBox() self.screenshot_box.set_layout(Gtk.ButtonBoxStyle.CENTER) self.screenshot_box.set_spacing(20) self.pack_screenshot_buttons() self.screenshot_box.set_margin_bottom(20) self._grid.attach(self.screenshot_box, 0, 2, 1, 1) # Create grey box to put the button in self.bottom_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.bottom_box.pack_start(self._send_button.align, False, False, 0) bottom_background = Gtk.EventBox() bottom_background.get_style_context().add_class("grey") bottom_background.add(self.bottom_box) self._grid.attach(bottom_background, 0, 3, 1, 1) self._grid.set_row_spacing(0) self.set_main_widget(self._grid) # kano-profile stat collection try: from kano_profile.badges import increment_app_state_variable_with_dialog increment_app_state_variable_with_dialog('kano-feedback', 'starts', 1) except Exception: pass def screenshot_clicked(self, button=None, event=None): ''' Takes a screenshot while minimising the window ''' # minimise the window self.iconify() take_screenshot() self.include_screenshot() # restore the window self.deiconify() def attach_clicked(self, button=None, event=None): ''' Opens the File Chooser Dialog. If image selected then copy it to the feedback folder ''' screenshot = None # Open file manager dialog = Gtk.FileChooserDialog( _("Please choose a file"), # noqa: F821 self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) self.add_filters(dialog) response = dialog.run() if response == Gtk.ResponseType.OK: screenshot = dialog.get_filename() dialog.destroy() # Copy image file into feedback folder if screenshot is not None: copy_screenshot(screenshot) self.include_screenshot() def add_filters(self, dialog): ''' Add image type filters Used for the File Chooser Dialog ''' # Image filter filter_images = Gtk.FileFilter() filter_images.set_name("Images") filter_images.add_mime_type("image/png") filter_images.add_mime_type("image/jpeg") filter_images.add_mime_type("image/gif") filter_images.add_pattern("*.png") filter_images.add_pattern("*.jpg") filter_images.add_pattern("*.gif") filter_images.add_pattern("*.tif") filter_images.add_pattern("*.xpm") dialog.add_filter(filter_images) # Any file filter filter_any = Gtk.FileFilter() filter_any.set_name("Any files") filter_any.add_pattern("*") dialog.add_filter(filter_any) def include_screenshot(self): ''' This is the box containing the filename of the screenshot, and the option to display it or remove it ''' if not hasattr(self, "screenshot"): # We pack the buttons into an event box with styling # so that we can have a container with a border radius # without having ugly gaps between the buttons. self.screenshot = Gtk.EventBox() self.screenshot.get_style_context().add_class("kano_button") self.screenshot.get_style_context().add_class("blue_background") remove_screenshot = Gtk.Button() attach_cursor_events(remove_screenshot) remove_icon = Gtk.Image.new_from_file( "/usr/share/kano-feedback/media/icons/close.png") remove_screenshot.add(remove_icon) remove_screenshot.connect("button-release-event", self.remove_screenshot) remove_screenshot.get_style_context().add_class("blue_background") show_screenshot = Gtk.Button() attach_cursor_events(show_screenshot) show_icon = Gtk.Image() show_icon.set_from_file( "/usr/share/kano-feedback/media/icons/preview.png") show_screenshot.add(show_icon) show_screenshot.connect("button-release-event", self.show_screenshot) show_screenshot.get_style_context().add_class("blue_background") label = Gtk.Label(SCREENSHOT_NAME.upper()) label.set_padding(10, 0) box = Gtk.Box() box.pack_start(label, False, False, 0) box.pack_end(remove_screenshot, False, False, 0) box.pack_end(show_screenshot, False, False, 0) self.screenshot.add(box) self.screenshot_box.remove(self._screenshot_button) self.screenshot_box.remove(self._attach_button) self.screenshot_box.pack_start(self.screenshot, False, False, 0) def remove_screenshot(self, widget, event): ''' Remove screenshot button action ''' delete_screenshot() self.screenshot_box.remove(self.screenshot) self.pack_screenshot_buttons() self.show_all() def show_screenshot(self, widget, event): ''' Creates and displays a dialog with the screenshot image ''' height = Gdk.Screen().get_default().get_height() width = Gdk.Screen().get_default().get_width() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( SCREENSHOT_PATH, width * 0.5, height * 0.5) image = Gtk.Image.new_from_pixbuf(pixbuf) dialog = KanoDialog( _("Screenshot"), # noqa: F821 widget=image) dialog.run() def pack_screenshot_buttons(self): ''' Pack the screenshot buttons into a box ''' self.screenshot_box.pack_start(self._screenshot_button, False, False, 0) self.screenshot_box.set_child_non_homogeneous(self._screenshot_button, True) self.screenshot_box.pack_start(self._attach_button, False, False, 0) self.screenshot_box.set_child_non_homogeneous(self._attach_button, True) def _pii_allowed(self): """ Helper to check whether or not users are allowed to send us text, screenshots, images. """ return (self.kano_world_client.is_logged_in() and self.kano_world_client.get_account_verified())
class SetProxy(Gtk.Box): def __init__(self, win): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.kano_button = KanoButton() self.win = win self.win.set_main_widget(self) self.heading = Heading(_("Proxy"), _("Connect via a friend")) grid = Gtk.Grid(column_homogeneous=False, column_spacing=10, row_spacing=10) self.kano_button.connect('button-release-event', self.apply_changes) self.kano_button.connect('key-release-event', self.apply_changes) self.win.top_bar.enable_prev() self.win.change_prev_callback(self.go_to_wifi) self.ip_entry = Gtk.Entry() self.ip_entry.props.placeholder_text = _("IP address") self.ip_entry.connect('key-release-event', self.proxy_enabled) self.username_entry = Gtk.Entry() self.username_entry.props.placeholder_text = _("Username") self.username_entry.connect('key-release-event', self.proxy_enabled) self.port_entry = Gtk.Entry() self.port_entry.props.placeholder_text = _("Port") self.port_entry.connect('key-release-event', self.proxy_enabled) self.password_entry = Gtk.Entry() self.password_entry.props.placeholder_text = _("Password") self.password_entry.set_visibility(False) self.password_entry.connect('key-release-event', self.proxy_enabled) password_box = Gtk.Box() password_box.add(self.password_entry) self.checkbutton = Gtk.CheckButton(_("enable proxy")) self.read_config() self.checkbutton.connect('clicked', self.proxy_status) self.checkbutton.set_can_focus(False) bottom_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) bottom_row.pack_start(self.checkbutton, False, False, 0) bottom_row.pack_start(self.kano_button, False, False, 60) bottom_row.set_margin_bottom(30) bottom_row.set_margin_left(70) grid.attach(self.ip_entry, 0, 0, 2, 2) grid.attach(self.username_entry, 0, 2, 2, 2) grid.attach(self.port_entry, 2, 0, 2, 2) grid.attach(password_box, 2, 2, 3, 2) grid_alignment = Gtk.Alignment(xscale=0, xalign=0.5, yscale=0, yalign=0.2) grid_alignment.add(grid) self.pack_start(self.heading.container, False, False, 0) self.pack_start(grid_alignment, True, True, 0) self.pack_end(bottom_row, False, False, 0) self.proxy_status(self.checkbutton) self.kano_button.set_sensitive(False) # Change text of kano button depending on if proxy is enabled if self.checkbutton.get_active(): self.kano_button.set_label(_("ENABLE PROXY")) else: self.kano_button.set_label(_("DISABLE PROXY")) self.win.show_all() def clear_entries(self): self.ip_entry.set_text("") self.username_entry.set_text("") self.port_entry.set_text("") self.password_entry.set_text("") def go_to_wifi(self, widget=None, event=None): self.win.clear_win() SetWifi(self.win) # Update for proxy def read_config(self): self.enable_proxy, data, _ = get_all_proxies() self.enabled_init = self.enable_proxy if self.enable_proxy: try: self.ip_entry.set_text(data['host']) self.port_entry.set_text(data['port']) if data['username']: self.username_entry.set_text(data['username']) if data['password']: self.password_entry.set_text(data['password']) except: # Something went wrong > disable proxy set_all_proxies(False) common.proxy_enabled = False self.enable_proxy = False self.enabled_init = False self.clear_entries() self.checkbutton.set_active(self.enable_proxy) def apply_changes(self, button, event): # If enter key is pressed or mouse button is clicked if not hasattr(event, 'keyval') or event.keyval == 65293: # This is a callback called by the main loop, so it's safe to # manipulate GTK objects: watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) self.win.get_window().set_cursor(watch_cursor) self.kano_button.start_spinner() self.kano_button.set_sensitive(False) def lengthy_process(): if self.enable_proxy: host = self.ip_entry.get_text() port = self.port_entry.get_text() username = self.username_entry.get_text() password = self.password_entry.get_text() set_all_proxies(enable=True, host=host, port=port, username=username, password=password) common.proxy_enabled = True success, text = test_proxy() if not success: title = _("Error with proxy") description = text return_value = 1 # disable proxy if we couldn't successfully enable it set_all_proxies(False) common.proxy_enabled = False else: title = _("Successfully enabled proxy") description = "" return_value = 0 else: set_all_proxies(False) common.proxy_enabled = False title = _("Successfully disabled proxy") description = "" return_value = 0 def done(title, description, return_value): kdialog = KanoDialog(title, description, [{ 'label': _("OK"), 'color': 'green', 'return_value': return_value }], parent_window=self.win) response = kdialog.run() self.win.get_window().set_cursor(None) self.kano_button.stop_spinner() if response == 0: self.go_to_wifi() elif response == 1: self.checkbutton.set_active(False) self.kano_button.set_sensitive(False) GObject.idle_add(done, title, description, return_value) thread = threading.Thread(target=lengthy_process) thread.start() # Validation functions # If the "enable proxy" checkbox is checked/uncheckout, this function is activated # Disables the text entries if enable proxy is not checked def proxy_status(self, widget): self.enable_proxy = widget.get_active() if self.enable_proxy: self.ip_entry.set_sensitive(True) self.port_entry.set_sensitive(True) self.password_entry.set_sensitive(True) self.username_entry.set_sensitive(True) # Run to see if it need enabling self.proxy_enabled() self.kano_button.set_label(_("ENABLE PROXY")) else: self.ip_entry.set_sensitive(False) self.port_entry.set_sensitive(False) self.password_entry.set_sensitive(False) self.username_entry.set_sensitive(False) self.kano_button.set_label(_("DISABLE PROXY")) self.kano_button.set_sensitive(True) # if proxy enabled: ip address, port are mandatory def proxy_enabled(self, widget=None, event=None): # Get IP address # Get port # Get # If these entries are non empty, good - else, disable the next button ip_text = self.ip_entry.get_text() port_text = self.port_entry.get_text() if ip_text == "" or port_text == "": self.kano_button.set_sensitive(False) return False else: self.kano_button.set_sensitive(True) return True return False
class LoginWithKanoWorldView(Gtk.Grid): def __init__(self, greeter): Gtk.Grid.__init__(self) self.get_style_context().add_class('password') self.set_row_spacing(12) self.greeter = greeter title = Heading(_('Login with Kano World'), _('Enter your Kano World details.')) self.attach(title.container, 0, 0, 1, 1) self.username = Gtk.Entry() self.username.set_placeholder_text('username') self.attach(self.username, 0, 1, 1, 1) self.password = Gtk.Entry() self.password.set_visibility(False) self.password.set_placeholder_text('password') self.attach(self.password, 0, 2, 1, 1) self.login_btn = KanoButton(_('LOGIN')) self.login_btn.connect('clicked', self._btn_login_pressed) self.attach(self.login_btn, 0, 3, 1, 1) def _btn_login_pressed(self, event=None, button=None): ''' Authenticates against Kano World. If successful synchronizes to a local Unix account, and tells lightdm to go forward with local a login. ''' logger.debug('Synchronizing Kano World account') self.login_btn.start_spinner() self.login_btn.set_sensitive(False) t = threading.Thread(target=self._thr_login) t.start() def _thr_login(self): loggedin = False reason = '' # TODO: Disable the "login" button unless these entry fields are non-empty # Collect credentials from the view self.unix_password = self.password.get_text() self.world_username = self.username.get_text() self.unix_username = self.username.get_text() atsign = self.unix_username.find('@') if atsign != -1: # For if we are in "staging" mode (see /etc/kano-world.conf) self.unix_username = self.unix_username[:atsign] # Now try to login to Kano World try: logger.debug('Authenticating user: {} to Kano World'.format( self.username.get_text())) (loggedin, reason) = kano_world_authenticate(self.username.get_text(), self.password.get_text()) logger.debug('Kano World auth response: {} - {}'.format( loggedin, reason)) except Exception as e: reason = str(e) logger.debug('Kano World auth Exception: {}'.format(reason)) pass if not loggedin: # Kano world auth unauthorized # FIXME: Localizing the below string fails with an exception GObject.idle_add(self._error_message_box, 'Failed to authenticate to Kano World', reason) return else: # We are authenticated to Kano World: proceed with forcing local user rc = -1 try: # Create the local unix user, bypass kano-init-flow, login & sync to Kano World createuser_cmd = 'sudo /usr/bin/kano-greeter-account {} {} {}'.format( self.unix_username, self.unix_password, self.world_username) _, _, rc = run_cmd(createuser_cmd) if rc == 0: logger.debug('Local user created correctly: {}'.format( self.unix_username)) elif rc == 1: logger.debug( 'Local user already exists, proceeding with login: {}'. format(self.unix_username)) created = True except: created = False if not created: logger.debug('Error creating new local user: {}'.format( self.unix_username)) GObject.idle_add(self._error_message_box, "Could not create local user", rc) return # Tell Lidghtdm to proceed with login session using the new user # We bind LightDM at this point only, this minimizes the number of attempts # to bind the Greeter class to a view, which he does not like quite well. logger.debug('Scheduling lightdm authentication in math thread') GObject.idle_add(self._auth_call) def _auth_call(self): logger.debug('Starting lightdm authentication') self._reset_greeter() self.greeter.authenticate(self.unix_username) if self.greeter.get_is_authenticated(): logger.debug('User is already authenticated, starting session') def _reset_greeter(self): # connect signal handlers to LightDM self.cb_one = self.greeter.connect('show-prompt', self._send_password_cb) self.cb_two = self.greeter.connect('authentication-complete', self._authentication_complete_cb) self.cb_three = self.greeter.connect('show-message', self._auth_error_cb) self.greeter.connect_sync() return (self.cb_one, self.cb_two, self.cb_three) def _send_password_cb(self, _greeter, text, prompt_type): logger.debug('Need to show prompt: {}'.format(text)) if _greeter.get_in_authentication(): logger.debug('Sending password to LightDM') _greeter.respond(self.unix_password) def _authentication_complete_cb(self, _greeter): logger.debug('Authentication process is complete') if not _greeter.get_is_authenticated(): logger.warn('Could not authenticate user {}'.format( self.unix_username)) self._auth_error_cb( _('Incorrect password (The default is "kano")')) return logger.info( 'The user {} is authenticated. Starting LightDM X Session'.format( self.unix_username)) set_last_user(self.unix_username) if not _greeter.start_session_sync('lightdm-xsession'): logger.error('Failed to start session') else: logger.info('Login failed') def _auth_error_cb(self, text, message_type=None): logger.info('There was an error logging in: {}'.format(text)) win = self.get_toplevel() win.go_to_users() self.login_btn.stop_spinner() self.login_btn.set_sensitive(True) self.newuser_btn.set_sensitive(True) error = KanoDialog(title_text=_('Error Synchronizing account'), description_text=text, parent_window=self.get_toplevel()) error.dialog.set_position(Gtk.WindowPosition.CENTER_ALWAYS) error.run() def _error_message_box(self, title, description): ''' Show a standard error message box ''' self.login_btn.stop_spinner() self.login_btn.set_sensitive(True) errormsg = KanoDialog(title_text=title, description_text=description, button_dict=[{ 'label': _('OK').upper(), 'color': 'red', 'return_value': True }]) errormsg.dialog.set_position(Gtk.WindowPosition.CENTER_ALWAYS) errormsg.run() # Clean up password field self.password.set_text('') return def grab_focus(self): ''' Clear username and password previous text, and gain focus. ''' self.username.set_text('') self.password.set_text('')
class PasswordScreen(Gtk.Box): def __init__(self, win, wiface, network_name, encryption, wrong_password=False): ''' Show the screen with the option of adding a password and connecting to a network ''' Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self._win = win self._win.set_main_widget(self) self._win.top_bar.enable_prev() self._wiface = wiface self._network_name = network_name self._encryption = encryption # Keep track if the user has already entered the wrong password before # so that we only pack the "password incorrect" label once self._wrong_password_used_before = False self._heading = Heading(_("Connect to the network"), self._network_name, self._win.is_plug(), True) self._heading.set_prev_callback(self._refresh_networks) self._heading.container.set_margin_right(20) self._heading.container.set_margin_left(20) if wrong_password: image_path = os.path.join(img_dir, "password-fail.png") wrong_password = self._create_wrong_password_label() self._heading.container.pack_start(wrong_password, True, True, 0) else: image_path = os.path.join(img_dir, "password.png") self._padlock_image = Gtk.Image.new_from_file(image_path) self._password_entry = Gtk.Entry() self._password_entry.set_placeholder_text(_("Password")) self._password_entry.set_visibility(False) self._password_entry.get_style_context().add_class('password_entry') self._password_entry.set_margin_left(60) self._password_entry.set_margin_right(60) self._password_entry.connect('key-release-event', self._set_button_sensitive) # If Enter key is pressed on the password entry, we want to act as # though the connect_btn was clicked self._password_entry.connect('key-release-event', self._on_connect_key_wrapper) self._connect_btn = KanoButton(_("CONNECT")) self._connect_btn.connect('clicked', self._on_connect) self._connect_btn.set_sensitive(False) self._connect_btn.set_margin_right(100) self._connect_btn.set_margin_left(100) self._connect_btn.pack_and_align() self._show_password = Gtk.CheckButton.new_with_label( _("Show password")) self._show_password.get_style_context().add_class('show_password') self._show_password.connect('toggled', self._change_password_entry_visiblity) self._show_password.set_active(True) self._show_password.set_margin_left(100) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.add(vbox) vbox.pack_start(self._heading.container, False, False, 10) vbox.pack_start(self._padlock_image, False, False, 10) vbox.pack_start(self._password_entry, False, False, 10) vbox.pack_start(self._show_password, False, False, 10) vbox.pack_end(self._connect_btn.align, False, False, 40) # Entry should have the keyboard focus self._password_entry.grab_focus() self.show_all() def _create_wrong_password_label(self): label = Gtk.Label(_("Password incorrect")) label.get_style_context().add_class('wrong_password_label') return label def _change_password_entry_visiblity(self, widget): ''' Depending on the checkbox, change the writing in the password entry to be readable. ''' visibility = self._show_password.get_active() self._password_entry.set_visibility(visibility) def _refresh_networks(self, widget=None): from kano_wifi_gui.RefreshNetworks import RefreshNetworks RefreshNetworks(self._win) def _on_connect_key_wrapper(self, widget, event): if event.keyval == Gdk.KEY_Return: self._on_connect() def _on_connect(self, widget=None): passphrase = self._password_entry.get_text() ConnectToNetwork(self._win, self._network_name, passphrase, self._encryption) def _set_button_sensitive(self, widget, event): ''' Enable the Connect button only if the passphrase is non empty ''' self._connect_btn.set_sensitive(widget.get_text_length() > 0) def _thread_finish(self, success): if success: self._success_screen() else: self._wrong_password_screen() def _success_screen(self): self._win.remove_main_widget() title = _("Success!") description = _("You're connected") buttons = [{ 'label': _("OK"), 'color': 'green', 'type': 'KanoButton', 'callback': Gtk.main_quit }] img_path = os.path.join(img_dir, "internet.png") # Track that user connected online track_action('internet-connection-established') self._win.set_main_widget( Template(title, description, buttons, self._win.is_plug(), img_path)) def _disable_widgets_start_spinner(self): self._connect_btn.start_spinner() self._connect_btn.set_sensitive(False) self._win.top_bar.prev_button.set_sensitive(False) self._password_entry.set_sensitive(False) self._show_password.set_sensitive(False) def _enable_widgets_stop_spinner(self): self._connect_btn.stop_spinner() self._connect_btn.set_sensitive(True) self._win.top_bar.prev_button.set_sensitive(True) self._password_entry.set_sensitive(True) self._show_password.set_sensitive(True)
class SetProxy(Gtk.Box): def __init__(self, win): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.kano_button = KanoButton() self.win = win self.win.set_main_widget(self) self.heading = Heading( _("Proxy"), _("Connect via a friend") ) grid = Gtk.Grid(column_homogeneous=False, column_spacing=10, row_spacing=10) self.kano_button.connect('button-release-event', self.apply_changes) self.kano_button.connect('key-release-event', self.apply_changes) self.win.top_bar.enable_prev() self.win.change_prev_callback(self.go_to_wifi) self.ip_entry = Gtk.Entry() self.ip_entry.props.placeholder_text = _("IP address") self.ip_entry.connect('key-release-event', self.proxy_enabled) self.username_entry = Gtk.Entry() self.username_entry.props.placeholder_text = _("Username") self.username_entry.connect('key-release-event', self.proxy_enabled) self.port_entry = Gtk.Entry() self.port_entry.props.placeholder_text = _("Port") self.port_entry.connect('key-release-event', self.proxy_enabled) self.password_entry = Gtk.Entry() self.password_entry.props.placeholder_text = _("Password") self.password_entry.set_visibility(False) self.password_entry.connect('key-release-event', self.proxy_enabled) password_box = Gtk.Box() password_box.add(self.password_entry) self.checkbutton = Gtk.CheckButton(_("enable proxy")) self.read_config() self.checkbutton.connect('clicked', self.proxy_status) self.checkbutton.set_can_focus(False) bottom_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) bottom_row.pack_start(self.checkbutton, False, False, 0) bottom_row.pack_start(self.kano_button, False, False, 60) bottom_row.set_margin_bottom(30) bottom_row.set_margin_left(70) grid.attach(self.ip_entry, 0, 0, 2, 2) grid.attach(self.username_entry, 0, 2, 2, 2) grid.attach(self.port_entry, 2, 0, 2, 2) grid.attach(password_box, 2, 2, 3, 2) grid_alignment = Gtk.Alignment(xscale=0, xalign=0.5, yscale=0, yalign=0.2) grid_alignment.add(grid) self.pack_start(self.heading.container, False, False, 0) self.pack_start(grid_alignment, True, True, 0) self.pack_end(bottom_row, False, False, 0) self.proxy_status(self.checkbutton) self.kano_button.set_sensitive(False) # Change text of kano button depending on if proxy is enabled if self.checkbutton.get_active(): self.kano_button.set_label(_("ENABLE PROXY")) else: self.kano_button.set_label(_("DISABLE PROXY")) self.win.show_all() def clear_entries(self): self.ip_entry.set_text("") self.username_entry.set_text("") self.port_entry.set_text("") self.password_entry.set_text("") def go_to_wifi(self, widget=None, event=None): self.win.clear_win() SetWifi(self.win) # Update for proxy def read_config(self): self.enable_proxy, data, _ = get_all_proxies() self.enabled_init = self.enable_proxy if self.enable_proxy: try: self.ip_entry.set_text(data['host']) self.port_entry.set_text(data['port']) if data['username']: self.username_entry.set_text(data['username']) if data['password']: self.password_entry.set_text(data['password']) except: # Something went wrong > disable proxy set_all_proxies(False) common.proxy_enabled = False self.enable_proxy = False self.enabled_init = False self.clear_entries() self.checkbutton.set_active(self.enable_proxy) def apply_changes(self, button, event): # If enter key is pressed or mouse button is clicked if not hasattr(event, 'keyval') or event.keyval == 65293: # This is a callback called by the main loop, so it's safe to # manipulate GTK objects: watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) self.win.get_window().set_cursor(watch_cursor) self.kano_button.start_spinner() self.kano_button.set_sensitive(False) def lengthy_process(): if self.enable_proxy: host = self.ip_entry.get_text() port = self.port_entry.get_text() username = self.username_entry.get_text() password = self.password_entry.get_text() set_all_proxies(enable=True, host=host, port=port, username=username, password=password) common.proxy_enabled = True success, text = test_proxy() if not success: title = _("Error with proxy") description = text return_value = 1 # disable proxy if we couldn't successfully enable it set_all_proxies(False) common.proxy_enabled = False else: title = _("Successfully enabled proxy") description = "" return_value = 0 else: set_all_proxies(False) common.proxy_enabled = False title = _("Successfully disabled proxy") description = "" return_value = 0 def done(title, description, return_value): kdialog = KanoDialog( title, description, [ { 'label': _("OK"), 'color': 'green', 'return_value': return_value } ], parent_window=self.win ) response = kdialog.run() self.win.get_window().set_cursor(None) self.kano_button.stop_spinner() if response == 0: self.go_to_wifi() elif response == 1: self.checkbutton.set_active(False) self.kano_button.set_sensitive(False) GObject.idle_add(done, title, description, return_value) thread = threading.Thread(target=lengthy_process) thread.start() # Validation functions # If the "enable proxy" checkbox is checked/uncheckout, this function is activated # Disables the text entries if enable proxy is not checked def proxy_status(self, widget): self.enable_proxy = widget.get_active() if self.enable_proxy: self.ip_entry.set_sensitive(True) self.port_entry.set_sensitive(True) self.password_entry.set_sensitive(True) self.username_entry.set_sensitive(True) # Run to see if it need enabling self.proxy_enabled() self.kano_button.set_label(_("ENABLE PROXY")) else: self.ip_entry.set_sensitive(False) self.port_entry.set_sensitive(False) self.password_entry.set_sensitive(False) self.username_entry.set_sensitive(False) self.kano_button.set_label(_("DISABLE PROXY")) self.kano_button.set_sensitive(True) # if proxy enabled: ip address, port are mandatory def proxy_enabled(self, widget=None, event=None): # Get IP address # Get port # Get # If these entries are non empty, good - else, disable the next button ip_text = self.ip_entry.get_text() port_text = self.port_entry.get_text() if ip_text == "" or port_text == "": self.kano_button.set_sensitive(False) return False else: self.kano_button.set_sensitive(True) return True return False
class SetAccount(Gtk.Box): def __init__(self, win): Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) self.win = win self.win.set_main_widget(self) self.win.top_bar.enable_prev() self.win.change_prev_callback(self.win.go_to_home) self.added_or_removed_account = False main_heading = Heading( _("System account settings"), _("Set your account") ) self.pass_button = KanoButton(_("CHANGE PASSWORD")) self.pass_button.pack_and_align() self.pass_button.connect('button-release-event', self.go_to_password_screen) self.pass_button.connect('key-release-event', self.go_to_password_screen) self.add_button = KanoButton(_("ADD ACCOUNT")) self.add_button.set_size_request(200, 44) self.add_button.connect('button-release-event', self.add_account) self.add_button.connect('key-release-event', self.add_account) self.remove_button = KanoButton(_("REMOVE ACCOUNT"), color='red') self.remove_button.set_size_request(200, 44) self.remove_button.connect('button-release-event', self.remove_account_dialog) self.remove_button.connect('key-release-event', self.remove_account_dialog) button_container = Gtk.Box() button_container.pack_start(self.add_button, False, False, 10) button_container.pack_start(self.remove_button, False, False, 10) button_align = Gtk.Alignment(xscale=0, xalign=0.5) button_align.add(button_container) accounts_heading = Heading( _("Accounts"), _("Add or remove accounts") ) # Check if we already scheduled an account add or remove # We import kano-init locally to avoid circular dependency # the packages. try: from kano_init.utils import is_any_task_scheduled if is_any_task_scheduled(): self.disable_buttons() except ImportError: self.disable_buttons() self.pack_start(main_heading.container, False, False, 0) self.pack_start(self.pass_button.align, False, False, 0) self.pack_start(accounts_heading.container, False, False, 0) self.pack_start(button_align, False, False, 0) self.win.show_all() def go_to_password_screen(self, widget, event): if not hasattr(event, 'keyval') or event.keyval == Gdk.KEY_Return: self.win.clear_win() SetPassword(self.win) # Gets executed when ADD button is clicked def add_account(self, widget=None, event=None): if not hasattr(event, 'keyval') or event.keyval == Gdk.KEY_Return: kdialog = None try: # add new user command add_user() except UserError as e: kdialog = kano_dialog.KanoDialog( _("Error creating new user"), str(e), parent_window=self.win ) else: kdialog = kano_dialog.KanoDialog( _("Reboot the system"), _("A new account will be created next time you reboot."), parent_window=self.win ) # Tell user to reboot to see changes common.need_reboot = True kdialog.run() self.disable_buttons() # Gets executed when REMOVE button is clicked def remove_account_dialog(self, widget=None, event=None): if not hasattr(event, 'keyval') or event.keyval == Gdk.KEY_Return: # Bring in message dialog box kdialog = kano_dialog.KanoDialog( _("Are you sure you want to delete the current user?"), _("You will lose all the data on this account!"), [ { 'label': _("CANCEL"), 'color': 'red', 'return_value': False }, { 'label': _("OK"), 'color': 'green', 'return_value': True } ], parent_window=self.win ) do_delete_user = kdialog.run() if do_delete_user: self.disable_buttons() try: delete_user() except UserError as e: kdialog = kano_dialog.KanoDialog( _("Error deleting user"), str(e), parent_window=self.win ) return kdialog = kano_dialog.KanoDialog( _("To finish removing this account, you need to reboot"), _("Do you want to reboot?"), [ { 'label': _("LATER"), 'color': 'grey', 'return_value': False }, { 'label': _("REBOOT NOW"), 'color': 'orange', 'return_value': True } ], parent_window=self.win ) do_reboot_now = kdialog.run() if do_reboot_now: os.system("sudo systemctl reboot") # Disables both buttons and makes the temp 'flag' folder def disable_buttons(self): self.add_button.set_sensitive(False) self.remove_button.set_sensitive(False) self.added_or_removed_account = True