huge code modification! do not test it for now:)

This commit is contained in:
Bilal Elmoussaoui 2017-01-17 00:30:36 +01:00
parent e1380cc11b
commit 56c5624cc4
28 changed files with 1508 additions and 1278 deletions

1
.gitignore vendored
View file

@ -105,3 +105,4 @@ po/Makefile.in.in
*.compiled
*.desktop
*.gresource
*.ui~

View file

@ -25,7 +25,7 @@ from gi.repository import Gtk, GLib, Gio, Gdk, GObject, GnomeKeyring as GK
from Authenticator.widgets.window import Window
from Authenticator.models.database import Database
from Authenticator.widgets.settings import SettingsWindow
from Authenticator.models.settings import SettingsReader
from Authenticator.const import settings
from Authenticator.interfaces.application_observrable import ApplicaitonObservable
from Authenticator.utils import *
import logging
@ -37,7 +37,6 @@ from os import environ as env
class Application(Gtk.Application):
win = None
alive = True
locked = False
settings_action = None
def __init__(self):
@ -51,8 +50,6 @@ class Application(Gtk.Application):
self.menu = Gio.Menu()
self.db = Database()
self.cfg = SettingsReader()
self.locked = self.cfg.read("state", "login")
result = GK.unlock_sync("org.gnome.Authenticator", None)
if result == GK.Result.CANCELLED:
@ -84,8 +81,6 @@ class Application(Gtk.Application):
settings_content = Gio.Menu.new()
settings_content.append_item(
Gio.MenuItem.new(_("Settings"), "app.settings"))
self.is_dark_mode_menu = Gio.MenuItem.new(_("Night mode"), "app.night_mode")
settings_content.append_item(self.is_dark_mode_menu)
settings_section = Gio.MenuItem.new_section(None, settings_content)
self.menu.append_item(settings_section)
@ -100,13 +95,9 @@ class Application(Gtk.Application):
help_section = Gio.MenuItem.new_section(None, help_content)
self.menu.append_item(help_section)
self.dark_mode_action = Gio.SimpleAction.new("night_mode", None)
self.dark_mode_action.connect("activate", self.enable_dark_mode)
self.add_action(self.dark_mode_action)
self.settings_action = Gio.SimpleAction.new("settings", None)
self.settings_action.connect("activate", self.on_settings)
self.settings_action.set_enabled(not self.locked)
self.settings_action.set_enabled(not settings.get_is_locked())
self.add_action(self.settings_action)
if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
@ -121,26 +112,10 @@ class Application(Gtk.Application):
action = Gio.SimpleAction.new("quit", None)
action.connect("activate", self.on_quit)
self.add_action(action)
self.refresh_menu_night_mode()
if not show_app_menu():
self.set_app_menu(self.menu)
logging.debug("Adding gnome shell menu")
def enable_dark_mode(self, *args):
is_dark_mode = self.cfg.read("night-mode", "preferences")
self.cfg.update("night-mode", not is_dark_mode, "preferences")
self.refresh_menu_night_mode()
def refresh_menu_night_mode(self):
is_dark_mode = self.cfg.read("night-mode", "preferences")
settings = Gtk.Settings.get_default()
settings.set_property("gtk-application-prefer-dark-theme", is_dark_mode)
if is_dark_mode:
self.is_dark_mode_menu.set_icon(Gio.ThemedIcon.new("emblem-ok-symbolic"))
else:
self.is_dark_mode_menu.set_icon(Gio.ThemedIcon.new(""))
#self.dark_mode_action.set_state(GLib.Variant.new_boolean(is_dark_mode))
def do_activate(self, *args):
if not self.win:
self.win = Window(self)
@ -150,8 +125,7 @@ class Application(Gtk.Application):
self.win.present()
def refresh_menu(self):
is_enabled = self.settings_action.get_enabled()
self.settings_action.set_enabled(not self.locked)
self.settings_action.set_enabled(not settings.get_is_locked())
def on_shortcuts(self, *args):
"""
@ -197,7 +171,6 @@ class Application(Gtk.Application):
"""
settings_window = SettingsWindow(self.win)
settings_window.show_window()
settings_window.present()
def on_quit(self, *args):
"""

3
Authenticator/const.py Normal file
View file

@ -0,0 +1,3 @@
from Authenticator.models.settings import Settings
settings = Settings.new()

View file

@ -1,4 +1,3 @@
from Authenticator.models.settings import SettingsReader
from Authenticator.models.code import Code
from Authenticator.models.database import Database
from Authenticator.models.observer import Observer
@ -25,8 +24,7 @@ class Account(GObject.GObject, Thread, Observer):
Thread.__init__(self)
GObject.GObject.__init__(self)
self.db = db
cfg = SettingsReader()
self.counter_max = cfg.read("refresh-time", "preferences")
self.counter_max = 30
self.counter = self.counter_max
self.account_id = app[0]
self.account_name = app[1]

View file

@ -17,52 +17,104 @@
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi.repository import Gtk, Gio
import logging
from gi.repository import Gio
from hashlib import sha256
class SettingsReader:
path = "org.gnome.Authenticator"
class Settings(Gio.Settings):
def __init__(self):
try:
# Check if the gsettings path exists
self.source = Gio.SettingsSchemaSource.get_default()
self.source.lookup(self.path, True)
except Exception as e:
logging.critical("Couldn't load gsettings source %s " % str(e))
Gio.Settings.__init__(self)
def read(self, key, path):
"""
Read a 'key' from org.gnome.Authenticator.'path'
:param key: (str) key to read
:param path: login/user
:return: value
"""
gsettings = Gio.Settings.new(self.path + "." + path)
value = gsettings.get_value(key)
value_type = value.get_type_string()
value = str(value).strip("'")
if value_type == "i":
return int(value)
elif value_type == "b":
return value == "true"
else:
return value
def new():
gsettings = Gio.Settings.new("org.gnome.Authenticator")
gsettings.__class__ = Settings
return gsettings
def update(self, key, value, path):
"""
Update 'key' value to 'value' from org.gnome.Authenticator.'path'
:param key: (str) key to read
:param value: updated value
:param path: login/user
:return: value
"""
gsettings = Gio.Settings.new(self.path + "." + path)
if type(value) is int:
gsettings.set_int(key, value)
elif type(value) is bool:
gsettings.set_boolean(key, value)
else:
gsettings.set_string(key, value)
gsettings.apply()
def get_window_size(self):
width, height = self.get_int("size-x"), self.get_int("size-y")
return width, height
def set_window_size(self, size):
width, height = size
self.set_int('size-x', width)
self.set_int('size-y', height)
self.apply()
def get_default_size(self):
width, height = self.get_default_value(
'size-x'), self.get_default_value('size-y')
return width.get_int32(), height.get_int32()
def get_window_position(self):
x, y = self.get_int('position-x'), self.get_int('position-y')
return x, y
def set_window_postion(self, position):
x, y = position
self.set_int('position-x', x)
self.set_int('position-y', y)
self.apply()
def set_view_mode(self, view_mode):
view_mode = view_mode.strip().lower()
if view_mode not in ["list", "grid"]:
view_mode = "list"
self.set_string("view-mode", view_mode)
self.apply()
def get_view_mode(self):
return self.get_string('view-mode').lower()
def set_can_be_locked(self, status):
self.set_boolean('state', status)
self.apply()
def get_can_be_locked(self):
return self.get_boolean('state')
def set_is_locked(self, statue):
self.set_boolean('locked', statue)
self.apply()
def get_is_locked(self):
return self.get_boolean('locked')
def set_is_night_mode(self, statue):
self.set_boolean('night-mode', statue)
self.apply()
def get_is_night_mode(self):
return self.get_boolean('night-mode')
def set_password(self, password):
password = sha256(password.encode('utf-8')).hexdigest()
self.set_string("password", password)
self.apply()
def compare_password(self, password):
password = sha256(password.encode('utf-8')).hexdigest()
return password == self.get_password()
def is_password_set(self):
return len(self.get_password()) != 0
def get_password(self):
return self.get_string("password")
def get_auto_lock_status(self):
return self.get_boolean("auto-lock")
def set_auto_lock_status(self, status):
self.set_boolean("auto-lock", status)
self.apply()
def get_auto_lock_time(self):
return self.get_int("auto-lock-time")
def set_auto_lock_time(self, auto_lock_time):
if auto_lock_time < 1 or auto_lock_time > 15:
auto_lock_time = 3
self.set_int("auto-lock-time", auto_lock_time)
self.apply()

View file

@ -17,16 +17,19 @@
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GLib, Pango
from Authenticator.widgets.confirmation import ConfirmationMessage
import logging
from Authenticator.models.observer import Observer
from Authenticator.utils import get_icon
from Authenticator.widgets.confirmation import ConfirmationMessage
from gettext import gettext as _
from gi import require_version
from gi.repository import GLib
from gi.repository import Gdk
from gi.repository import Pango
from threading import Thread
from time import sleep
import logging
from gettext import gettext as _
from Authenticator.models.observer import Observer
require_version("Gtk", "3.0")
from gi.repository import Gtk
class RowEntryName(Gtk.Entry):
@ -78,6 +81,10 @@ class AccountRow(Observer):
self.code_label = Gtk.Label(xalign=0)
self.timer_label = Gtk.Label(xalign=0)
self.is_grid_view = isinstance(self, AccountRowGrid)
self.accel = Gtk.AccelGroup()
self.window.add_accel_group(self.accel)
self.accel.connect(Gdk.keyval_from_name('C'), Gdk.ModifierType.CONTROL_MASK, 0, self.copy_code)
self.accel.connect(Gdk.keyval_from_name("Enter"), Gdk.ModifierType.META_MASK, 0, self.toggle_code)
def update(self, *args, **kwargs):
self.set_account_code(kwargs.get("code", None))
@ -127,6 +134,39 @@ class AccountRow(Observer):
"""
self.revealer.set_reveal_child(not self.revealer.get_reveal_child())
def on_key_press(self, widget, event):
keyname = Gdk.keyval_name(event.keyval).lower()
if not self.window.is_locked():
if self.parent.get_selected_row_id() == self.account.get_id():
is_search_bar = self.window.search_bar.is_visible()
is_editing_name = self.name_entry.is_visible()
if keyname == "delete" and not is_search_bar and not is_editing_name:
self.remove()
return True
if keyname == "escape":
if is_editing_name:
self.toggle_edit_mode(False)
return True
if event.state & Gdk.ModifierType.CONTROL_MASK:
if keyname == 'e':
self.edit()
return True
if keyname == "return":
if is_editing_name:
self.apply_edit_name()
else:
self.toggle_code_box()
return True
if event.state & Gdk.ModifierType.CONTROL_MASK:
if keyname == 'c':
self.copy_code()
return True
return False
def copy_code(self, *args):
"""
Copy code shows the code box for a while (10s by default)
@ -135,7 +175,7 @@ class AccountRow(Observer):
code = self.account.get_code()
try:
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.window.notification.update(
self.window.notification.set_message(
_('Code "{0}" copied to clipboard'.format(str(code))))
self.window.notification.show()
clipboard.clear()
@ -169,32 +209,60 @@ class AccountRow(Observer):
self.actions_box.set_visible(visible)
self.actions_box.set_no_show_all(not visible)
def toggle_edit_mode(self, visible):
if visible:
self.name_entry.show()
self.name_entry.set_text(self.account.get_name())
self.name_entry.focus()
else:
self.name_entry.hide()
self.application_name.set_visible(not visible)
self.application_name.set_no_show_all(visible)
if isinstance(self, AccountRowList):
self.apply_button.set_visible(visible)
self.apply_button.set_no_show_all(not visible)
self.edit_button.get_parent().set_visible(not visible)
self.edit_button.get_parent().set_no_show_all(visible)
def edit(self, *args):
is_visible = self.name_entry.is_visible()
self.toggle_edit_mode(not is_visible)
self.parent.select_row(self)
def apply_edit_name(self, *args):
new_name = self.name_entry.get_text()
self.application_name.set_text(new_name)
self.account.set_name(new_name)
self.toggle_edit_mode(False)
def remove(self, *args):
"""
Remove an account
"""
message = _('Do you really want to remove "%s"?' % self.account.get_name())
message = _('Do you really want to remove "%s"?' %
self.account.get_name())
confirmation = ConfirmationMessage(self.window, message)
confirmation.show()
if confirmation.get_confirmation():
self.window.notification.update(_('"%s" was removed' % self.account.get_name()), self.undo_remove)
self.window.notification.set_message(
_('"%s" was removed' % self.account.get_name()))
self.window.notification.set_undo_action(self.undo_remove)
self.window.notification.show()
self.remove_timer = self.window.notification.timeout
GLib.timeout_add_seconds(1, self.update_remove_countdown)
confirmation.destroy()
def undo_remove(self):
self.remove_timer = 0
self.window.notification.hide()
class AccountRowList(AccountRow, Gtk.ListBoxRow):
class AccountRowList(Gtk.ListBoxRow, AccountRow):
def __init__(self, parent, window, account):
AccountRow.__init__(self, parent, window, account)
Gtk.ListBoxRow.__init__(self)
AccountRow.__init__(self, parent, window, account)
self.create_row()
self.window.connect("key-press-event", self.__on_key_press)
def create_row(self):
"""
@ -293,73 +361,19 @@ class AccountRowList(AccountRow, Gtk.ListBoxRow):
self.revealer.set_reveal_child(False)
self.add(box)
def toggle_edit_mode(self, visible):
if visible:
self.name_entry.show()
self.name_entry.set_text(self.account.get_name())
self.name_entry.focus()
else:
self.name_entry.hide()
self.application_name.set_visible(not visible)
self.application_name.set_no_show_all(visible)
self.apply_button.set_visible(visible)
self.apply_button.set_no_show_all(not visible)
self.edit_button.get_parent().set_visible(not visible)
self.edit_button.get_parent().set_no_show_all(visible)
def __on_key_press(self, widget, event):
keyname = Gdk.keyval_name(event.keyval).lower()
if not self.window.is_locked():
if self.parent.get_selected_row_id() == self.account.get_id():
is_search_bar = self.window.search_bar.is_visible()
is_editing_name = self.name_entry.is_visible()
if keyname == "delete" and not is_search_bar and not is_editing_name:
self.remove()
return True
if keyname == "escape":
if is_editing_name:
self.toggle_edit_mode(False)
return True
if keyname == "return":
if is_editing_name:
self.apply_edit_name()
else:
self.toggle_code_box()
return True
if event.state & Gdk.ModifierType.CONTROL_MASK:
if keyname == 'c':
self.copy_code()
return True
return False
def edit(self, *args):
is_visible = self.name_entry.is_visible()
self.toggle_edit_mode(not is_visible)
self.parent.select_row(self)
def apply_edit_name(self, *args):
new_name = self.name_entry.get_text()
self.application_name.set_text(new_name)
self.account.set_name(new_name)
self.toggle_edit_mode(False)
class AccountRowGrid(AccountRow, Gtk.Box):
class AccountRowGrid(AccountRow, Gtk.FlowBoxChild):
def __init__(self, parent, window, account):
Gtk.FlowBoxChild.__init__(self)
AccountRow.__init__(self, parent, window, account)
Gtk.Box.__init__(self)
self.create_row()
self.window.connect("key-press-event", self.__on_key_press)
def create_row(self):
"""
Create ListBoxRow
"""
self.actions_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
v_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# Checkbox
@ -379,7 +393,13 @@ class AccountRowGrid(AccountRow, Gtk.Box):
auth_logo.set_from_pixbuf(auth_icon)
logo_box.add(auth_logo)
overlay_box.add(logo_box)
v_box.pack_start(overlay_box, False, False, 6)
# Account name entry
name_entry_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.name_entry = RowEntryName(self.account.get_name())
name_entry_box.pack_start(self.name_entry, False, False, 6)
v_box.pack_start(name_entry_box, False, False, 0)
name_entry_box.set_visible(False)
# accout name
name_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
@ -390,53 +410,28 @@ class AccountRowGrid(AccountRow, Gtk.Box):
name_event.connect("button-press-event", self.toggle_code)
name_event.add(self.application_name)
name_box.pack_start(name_event, True, False, 6)
v_box.pack_start(name_box, False, False, 0)
# Copy button
copy_event = Gtk.EventBox()
copy_button = Gtk.Image(xalign=0)
copy_button.set_from_icon_name(
"edit-copy-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
copy_button.set_tooltip_text(_("Copy the generated code"))
copy_event.connect("button-press-event", self.copy_code)
copy_event.add(copy_button)
self.actions_box.pack_end(copy_event, False, False, 6)
# timer
self.set_account_counter(self.account.get_counter())
self.timer_label.get_style_context().add_class("account-timer-grid")
self.code_box.pack_end(self.timer_label, False, True, 6)
# secret code
self.code_label.get_style_context().add_class("account-secret-code-grid")
self.code_box.pack_start(self.code_label, True, False, 6)
if self.account.code_generated:
self.set_account_code(self.account.get_code())
else:
self.set_account_code(_("Error during the generation of code"))
self.code_box.pack_end(self.timer_label, False, True, 6)
self.code_box.pack_start(self.code_label, True, False, 6)
self.revealer.add(self.code_box)
self.revealer.set_reveal_child(False)
v_box.pack_start(overlay_box, False, False, 6)
v_box.pack_start(name_entry_box, False, False, 0)
v_box.pack_start(name_box, False, False, 0)
v_box.pack_start(self.revealer, False, False, 0)
main_box.pack_end(v_box, True, False, 0)
self.add(main_box)
# self.pack_start(checkbox_box, False, False, 0)
self.pack_start(v_box, True, False, 0)
def __on_key_press(self, widget, event):
keyname = Gdk.keyval_name(event.keyval).lower()
if not self.window.is_locked():
if self.parent.get_selected_row_id() == self.account.get_id():
is_search_bar = self.window.search_bar.is_visible()
if keyname == "delete" and not is_search_bar:
self.remove()
return True
if keyname == "return":
self.toggle_code_box()
return True
if event.state & Gdk.ModifierType.CONTROL_MASK:
if keyname == 'c':
self.copy_code()
return True
return False

View file

@ -0,0 +1,289 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
import logging
from Authenticator.const import settings
from Authenticator.widgets.account_row import AccountRowGrid, AccountRowList
from Authenticator.widgets.confirmation import ConfirmationMessage
from gettext import gettext as _
from gi.repository import GLib, GObject, Gdk, Gio, Gtk
from hashlib import sha256
class AccountsGrid(Gtk.FlowBox):
def __init__(self, window, accounts):
self.accounts = accounts
self.window = window
self.generate()
self.accel = Gtk.AccelGroup()
self.window.add_accel_group(self.accel)
self.connect("child-activated", self.activate_child)
self.connect("selected-children-changed", self.selected_child)
self.accel.connect(Gdk.keyval_from_name('Up'), Gdk.ModifierType.META_MASK, 0, self.navigate)
self.accel.connect(Gdk.keyval_from_name('Down'), Gdk.ModifierType.META_MASK, 0, self.navigate)
self.accel.connect(Gdk.keyval_from_name('Left'), Gdk.ModifierType.META_MASK, 0, self.navigate)
self.accel.connect(Gdk.keyval_from_name('Right'), Gdk.ModifierType.META_MASK, 0, self.navigate)
def navigate(self, *args):
"""
Keyboard Listener handling
"""
keyname = Gdk.keyval_name(args[2]).lower()
if not settings.get_is_locked():
count = len(self.get_children())
selected_row = self.get_selected_children()
if selected_row is not None:
index = selected_row[0].get_index()
child_per_line = self.get_children_per_line()
if keyname in ["up", "down"]:
dy = -child_per_line if keyname == "up" else child_per_line
new_index = (index + dy) % count
else:
dx = -1 if keyname == "left" else 1
new_index = (index + dx) % count
self.select_child(self.get_child_at_index(new_index))
return True
return False
def generate(self):
Gtk.FlowBox.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.set_min_children_per_line(2)
self.set_max_children_per_line(8)
self.set_valign(Gtk.Align.START)
self.set_column_spacing(0)
self.set_row_spacing(0)
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.set_homogeneous(True)
count = len(self.accounts)
for account in self.accounts:
self.add(AccountRowGrid(self, self.window, account))
if count != 0:
self.select_child(self.get_child_at_index(0))
def selected_child(self, account_list):
for row in self.get_children():
checkbutton = row.get_checkbox()
if not checkbutton.get_active() and self.window.is_select_mode:
self.unselect_child(row)
def get_children_per_line(self):
total = len(self.get_children())
child_allocated_width = self.get_children()[0].get_allocated_width()
window_allocated_width = self.window.get_allocated_width()
return round(window_allocated_width/child_allocated_width,0)
def activate_child(self, account_list, selected_child):
if self.window.is_select_mode and selected_child:
self.select_account(selected_child.get_checkbox())
def toggle_select_mode(self):
is_select_mode = self.window.is_select_mode
if is_select_mode:
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
else:
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
if len(self.get_children()) != 0:
self.select_child(self.get_child_at_index(0))
for row in self.get_children():
checkbox = row.get_checkbox()
code_label = row.get_code_label()
visible = checkbox.get_visible()
style_context = code_label.get_style_context()
if is_select_mode:
self.select_account(checkbox)
row.toggle_action_box(visible)
checkbox.set_visible(not visible)
checkbox.set_no_show_all(visible)
def remove_selected(self, *args):
"""
Remove selected accounts
"""
print(self.get_selected_children())
for row in self.get_selected_children():
checkbox = row.get_checkbox()
if checkbox.get_active():
row.remove()
self.unselect_all()
self.window.toggle_select()
def select_account(self, checkbutton):
"""
Select an account
:param checkbutton:
"""
is_active = checkbutton.get_active()
is_visible = checkbutton.get_visible()
flowbox_child = checkbutton.get_parent().get_parent().get_parent().get_parent()
if is_active:
self.select_child(flowbox_child)
else:
self.unselect_child(flowbox_child)
selected_count = len(self.get_selected_children())
self.window.hb.remove_button.set_sensitive(selected_count > 0)
def get_selected_child_id(self):
selected_box = self.get_selected_children()
if selected_box:
return selected_box[0].account.get_id()
else:
return None
def get_selected_row_id(self):
return self.get_selected_child_id()
def append(self, account):
self.add(AccountRowGrid(self, self.window, account))
def remove_by_id(self, _id):
for row in self.get_children():
if row.account.get_id() == _id:
self.remove(row)
break
class AccountsList(Gtk.ListBox):
def __init__(self, window, accounts):
self.accounts = accounts
self.window = window
self.generate()
self.accel = Gtk.AccelGroup()
self.window.add_accel_group(self.accel)
self.connect("row-activated", self.activate_row)
self.connect("row-selected", self.selected_row)
self.accel.connect(Gdk.keyval_from_name('Up'), Gdk.ModifierType.META_MASK, 0, self.navigate)
self.accel.connect(Gdk.keyval_from_name('Down'), Gdk.ModifierType.META_MASK, 0, self.navigate)
def generate(self):
Gtk.ListBox.__init__(self)
self.get_style_context().add_class("applications-list")
self.set_adjustment()
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
count = len(self.accounts)
for account in self.accounts:
self.add(AccountRowList(self, self.window, account))
if count != 0:
self.select_row(self.get_row_at_index(0))
def selected_row(self, account_list, selected_row):
for row in self.get_children():
if row != selected_row:
row.toggle_edit_mode(False)
checkbutton = row.get_checkbox()
if not checkbutton.get_active() and self.window.is_select_mode:
self.unselect_row(row)
def activate_row(self, account_list, selected_row):
if self.window.is_select_mode and selected_row:
self.select_account(selected_row.get_checkbox())
def navigate(self, *args):
"""
Keyboard Listener handling
"""
keyname = Gdk.keyval_name(args[2]).lower()
if not settings.get_is_locked():
count = len(self.get_children())
dx = -1 if keyname == "up" else 1
selected_row = self.get_selected_row()
if selected_row is not None:
index = selected_row.get_index()
new_index = (index + dx) % count
self.select_row(self.get_row_at_index(new_index))
return True
return False
def toggle_select_mode(self):
is_select_mode = self.window.is_select_mode
if is_select_mode:
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
else:
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
if len(self.get_children()) != 0:
self.select_row(self.get_row_at_index(0))
for row in self.get_children():
checkbox = row.get_checkbox()
code_label = row.get_code_label()
visible = checkbox.get_visible()
style_context = code_label.get_style_context()
if is_select_mode:
self.select_account(checkbox)
style_context.add_class("application-secret-code-select-mode")
else:
style_context.remove_class(
"application-secret-code-select-mode")
row.toggle_action_box(visible)
checkbox.set_visible(not visible)
checkbox.set_no_show_all(visible)
def remove_selected(self, *args):
"""
Remove selected accounts
"""
for row in self.get_selected_rows():
checkbox = row.get_checkbox()
if checkbox.get_active():
row.remove()
self.unselect_all()
self.window.toggle_select()
def select_account(self, checkbutton):
"""
Select an account
:param checkbutton:
"""
is_active = checkbutton.get_active()
is_visible = checkbutton.get_visible()
listbox_row = checkbutton.get_parent().get_parent().get_parent()
if is_active:
self.select_row(listbox_row)
else:
self.unselect_row(listbox_row)
selected_count = len(self.get_selected_rows())
self.window.hb.remove_button.set_sensitive(selected_count > 0)
def get_selected_row_id(self):
selected_row = self.get_selected_row()
if selected_row:
return selected_row.account.get_id()
else:
return None
def append(self, account):
self.add(AccountRowList(self, self.window, account))
def remove_by_id(self, _id):
for row in self.get_children():
if row.account.get_id() == _id:
self.remove(row)
break

View file

@ -1,168 +0,0 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
from Authenticator.widgets.confirmation import ConfirmationMessage
from Authenticator.widgets.account_row import AccountRowGrid
from gettext import gettext as _
from hashlib import sha256
import logging
class AccountsGrid(Gtk.FlowBox):
scrolled_win = None
def __init__(self, window, accounts):
self.accounts = accounts
self.window = window
self.generate()
self.connect("child-activated", self.activate_child)
self.connect("selected-children-changed", self.selected_child)
GLib.timeout_add_seconds(1, self.refresh)
def generate(self):
Gtk.FlowBox.__init__(self,orientation=Gtk.Orientation.HORIZONTAL)
# Create a ScrolledWindow for accounts
self.set_min_children_per_line(2)
self.set_max_children_per_line(8)
self.set_valign(Gtk.Align.START)
self.set_column_spacing(0)
self.set_row_spacing(0)
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.scrolled_win = Gtk.ScrolledWindow()
self.scrolled_win.add_with_viewport(self)
self.set_homogeneous(True)
count = len(self.accounts)
for account in self.accounts:
self.add(AccountRowGrid(self, self.window, account))
if count != 0:
self.select_child(self.get_child_at_index(0))
self.show_all()
def selected_child(self, account_list):
for box in self.get_children():
row = box.get_children()[0]
checkbutton = row.get_checkbox()
if not checkbutton.get_active() and self.window.is_select_mode:
self.unselect_child(box)
def activate_child(self, account_list, selected_child):
if self.window.is_select_mode and selected_child:
selected_box = selected_child.get_children()[0]
self.select_account(selected_box.get_checkbox())
def toggle_select_mode(self):
is_select_mode = self.window.is_select_mode
if is_select_mode:
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
else:
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
if len(self.get_children()) != 0:
self.select_child(self.get_child_at_index(0))
for box in self.get_children():
row = box.get_children()[0]
checkbox = row.get_checkbox()
code_label = row.get_code_label()
visible = checkbox.get_visible()
style_context = code_label.get_style_context()
if is_select_mode:
self.select_account(checkbox)
row.toggle_action_box(visible)
checkbox.set_visible(not visible)
checkbox.set_no_show_all(visible)
def remove_selected(self, *args):
"""
Remove selected accounts
"""
for row in self.get_selected_children():
checkbox = row.get_checkbox()
if checkbox.get_active():
row.remove()
self.unselect_all()
self.window.toggle_select()
self.window.refresh_window()
def select_account(self, checkbutton):
"""
Select an account
:param checkbutton:
"""
is_active = checkbutton.get_active()
is_visible = checkbutton.get_visible()
flowbox_child = checkbutton.get_parent().get_parent().get_parent().get_parent().get_parent()
if is_active:
self.select_child(flowbox_child)
else:
self.unselect_child(flowbox_child)
selected_count = len(self.get_selected_children())
self.window.hb.remove_button.set_sensitive(selected_count > 0)
def get_selected_child_id(self):
selected_box = self.get_selected_children()
if selected_box:
return selected_box[0].get_children()[0].account.get_id()
else:
return None
def get_selected_row_id(self):
return self.get_selected_child_id()
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
self.get_scrolled_win().set_visible(visible)
self.get_scrolled_win().set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def get_scrolled_win(self):
return self.scrolled_win
def refresh(self):
self.scrolled_win.hide()
self.scrolled_win.show_all()
def append(self, account):
self.add(AccountRowGrid(self, self.window, account))
self.refresh()
def remove_by_id(self, _id):
for row in self.get_children():
row = row.get_children()[0]
if row.account.get_id() == _id:
self.remove(row)
break
self.refresh()

View file

@ -1,182 +0,0 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
from Authenticator.widgets.confirmation import ConfirmationMessage
from Authenticator.widgets.account_row import AccountRowList
from gettext import gettext as _
from hashlib import sha256
import logging
class AccountsList(Gtk.ListBox):
scrolled_win = None
def __init__(self, window, accounts):
self.accounts = accounts
self.window = window
self.generate()
self.window.connect("key-press-event", self.on_key_press)
self.connect("row-activated", self.activate_row)
self.connect("row-selected", self.selected_row)
GLib.timeout_add_seconds(1, self.refresh)
def generate(self):
Gtk.ListBox.__init__(self)
# Create a ScrolledWindow for accounts
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.get_style_context().add_class("applications-list")
self.set_adjustment()
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
box.pack_start(self, True, True, 0)
self.scrolled_win = Gtk.ScrolledWindow()
self.scrolled_win.add_with_viewport(box)
count = len(self.accounts)
for account in self.accounts:
self.add(AccountRowList(self, self.window, account))
if count != 0:
self.select_row(self.get_row_at_index(0))
self.show_all()
def selected_row(self, account_list, selected_row):
for row in self.get_children():
if row != selected_row:
row.toggle_edit_mode(False)
checkbutton = row.get_checkbox()
if not checkbutton.get_active() and self.window.is_select_mode:
self.unselect_row(row)
def activate_row(self, account_list, selected_row):
if self.window.is_select_mode and selected_row:
self.select_account(selected_row.get_checkbox())
def on_key_press(self, app, key_event):
"""
Keyboard Listener handling
"""
keyname = Gdk.keyval_name(key_event.keyval).lower()
if not self.window.is_locked():
if not self.window.no_account_box.is_visible():
if keyname == "up" or keyname == "down":
count = len(self.get_children())
dx = -1 if keyname == "up" else 1
selected_row = self.get_selected_row()
if selected_row is not None:
index = selected_row.get_index()
new_index = (index + dx) % count
self.select_row(self.get_row_at_index(new_index))
return True
return False
def toggle_select_mode(self):
is_select_mode = self.window.is_select_mode
if is_select_mode:
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
else:
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
if len(self.get_children()) != 0:
self.select_row(self.get_row_at_index(0))
for row in self.get_children():
checkbox = row.get_checkbox()
code_label = row.get_code_label()
visible = checkbox.get_visible()
style_context = code_label.get_style_context()
if is_select_mode:
self.select_account(checkbox)
style_context.add_class("application-secret-code-select-mode")
else:
style_context.remove_class("application-secret-code-select-mode")
row.toggle_action_box(visible)
checkbox.set_visible(not visible)
checkbox.set_no_show_all(visible)
def remove_selected(self, *args):
"""
Remove selected accounts
"""
for row in self.get_selected_rows():
checkbox = row.get_checkbox()
if checkbox.get_active():
row.remove()
self.unselect_all()
self.window.toggle_select()
def select_account(self, checkbutton):
"""
Select an account
:param checkbutton:
"""
is_active = checkbutton.get_active()
is_visible = checkbutton.get_visible()
listbox_row = checkbutton.get_parent().get_parent().get_parent()
if is_active:
self.select_row(listbox_row)
else:
self.unselect_row(listbox_row)
selected_count = len(self.get_selected_rows())
self.window.hb.remove_button.set_sensitive(selected_count > 0)
def get_selected_row_id(self):
selected_row = self.get_selected_row()
if selected_row:
return selected_row.account.get_id()
else:
return None
def get_scrolled_win(self):
return self.scrolled_win
def toggle(self, visible):
self.get_scrolled_win().set_visible(visible)
self.get_scrolled_win().set_no_show_all(not visible)
self.set_visible(visible)
self.set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def refresh(self):
self.scrolled_win.hide()
self.scrolled_win.show_all()
def append(self, account):
self.add(AccountRowList(self, self.window, account))
def remove_by_id(self, _id):
for row in self.get_children():
if row.account.get_id() == _id:
self.remove(row)
break
self.refresh()

View file

@ -20,16 +20,17 @@
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
from Authenticator.widgets.accounts_list import AccountsList
from Authenticator.widgets.accounts_grid import AccountsGrid
from Authenticator.widgets.account_row import AccountRowGrid, AccountRowList
from Authenticator.widgets.search_bar import SearchBar
from Authenticator.models.account import Account
from gettext import gettext as _
from hashlib import sha256
import logging
from Authenticator.const import settings
from Authenticator.models.account import Account
from Authenticator.models.observer import Observer
from Authenticator.widgets.account_row import AccountRowGrid, AccountRowList
from Authenticator.widgets.accounts import AccountsGrid, AccountsList
from Authenticator.widgets.search_bar import SearchBar
from gettext import gettext as _
from gi.repository import GLib, GObject, Gdk, Gio, Gtk
from hashlib import sha256
class AccountsWindow(Gtk.Box, Observer):
@ -37,14 +38,18 @@ class AccountsWindow(Gtk.Box, Observer):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self.app = application
self.window = window
self.scrolled_win = None
self.generate()
def generate(self):
self.stack = Gtk.Stack()
self.scrolled_win = Gtk.ScrolledWindow()
self.stack.set_vexpand(False)
self.stack.set_hexpand(False)
self.stack.set_transition_duration(400)
self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
self.generate_accounts_list()
self.generate_search_bar()
self.pack_start(self.search_bar, False, True, 0)
self.reorder_child(self.search_bar, 0)
def generate_accounts_list(self):
"""
@ -52,6 +57,7 @@ class AccountsWindow(Gtk.Box, Observer):
"""
apps = self.app.db.fetch_apps()
count = len(apps)
self.scrolled_win = Gtk.ScrolledWindow()
self.accounts = []
for app in apps:
account = Account(app, self.app.db)
@ -62,11 +68,12 @@ class AccountsWindow(Gtk.Box, Observer):
self.accounts_list = AccountsList(self.window, self.accounts)
self.accounts_grid = AccountsGrid(self.window, self.accounts)
self.pack_start(self.accounts_list.get_scrolled_win(), True, True, 0)
self.pack_start(self.accounts_grid.get_scrolled_win(), True, True, 0)
is_grid = self.app.cfg.read(
"view-mode", "preferences").lower() == "grid"
self.set_mode_view(is_grid)
self.stack.add_named(self.accounts_list, "list")
self.stack.add_named(self.accounts_grid, "grid")
self.scrolled_win.add_with_viewport(self.stack)
self.pack_start(self.scrolled_win, True, True, 0)
self.set_mode_view(settings.get_view_mode(), True)
def generate_search_bar(self):
"""
@ -74,6 +81,8 @@ class AccountsWindow(Gtk.Box, Observer):
"""
self.search_bar = SearchBar(self.window, self.window.hb.search_button,
[self.accounts_list, self.accounts_grid])
self.pack_start(self.search_bar, False, True, 0)
self.reorder_child(self.search_bar, 0)
def update(self, *args, **kwargs):
removed_id = kwargs.get("removed", None)
@ -82,16 +91,17 @@ class AccountsWindow(Gtk.Box, Observer):
counter = kwargs.pop("counter", None)
view_mode = kwargs.pop("view_mode", None)
if counter == 0 or locked:
self.hide()
self.set_visible(False)
self.set_no_show_all(True)
elif unlocked or counter != 0:
self.show()
self.set_visible(True)
self.set_no_show_all(False)
if removed_id:
self.accounts_list.remove_by_id(removed_id)
self.accounts_grid.remove_by_id(removed_id)
self.window.emit("changed", True)
if view_mode:
self.set_mode_view(view_mode == "grid")
self.set_mode_view(view_mode)
def get_accounts_list(self):
return self.accounts_list
@ -99,40 +109,14 @@ class AccountsWindow(Gtk.Box, Observer):
def get_accounts_grid(self):
return self.accounts_grid
def set_mode_view(self, is_grid):
if is_grid:
self.scrolled_win = self.accounts_grid.get_scrolled_win()
self.accounts_list.hide()
self.accounts_grid.show()
self.accounts_grid.refresh()
else:
self.scrolled_win = self.accounts_list.get_scrolled_win()
self.accounts_grid.hide()
self.accounts_list.show()
self.accounts_list.refresh()
self.scrolled_win.set_no_show_all(False)
self.scrolled_win.set_visible(True)
def set_mode_view(self, view_mode, on_start=False):
self.stack.set_visible_child_name(view_mode)
if not on_start:
settings.set_view_mode(view_mode)
def get_search_bar(self):
return self.search_bar
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def refresh(self, *args):
self.accounts_list.refresh()
self.accounts_grid.refresh()
def append(self, app):
"""
Add an element to the ListBox

View file

@ -19,17 +19,24 @@
"""
import logging
import yaml
from glob import glob
from threading import Thread
from os import path, environ as env
from Authenticator.utils import screenshot_area
from Authenticator.widgets.applications_list import ApplicationChooserWindow
from Authenticator.widgets.inapp_notification import InAppNotification
from Authenticator.widgets.search_bar import SearchBar
from Authenticator.models.code import Code
from Authenticator.models.qr_reader import QRReader
from Authenticator.utils import get_icon
from gettext import gettext as _
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, Gio
from gi.repository import Gtk, GObject, Gio, Gdk, GLib
""" TODO :
add back button
rewrite the ui using Glade and Builder
"""
class AddAccount(Gtk.Window):
@ -144,7 +151,7 @@ class AddAccount(Gtk.Window):
self.secret_code.set_text(data["secret"])
self.apply_button.set_sensitive(True)
else:
self.notification.update(
self.notification.set_message(
_("Selected area is not a valid QR code"))
self.notification.set_message_type(Gtk.MessageType.ERROR)
self.notification.show()
@ -201,7 +208,8 @@ class AddAccount(Gtk.Window):
secret_code = self.secret_code.get_text()
logo = self.selected_logo if self.selected_logo else "image-missing"
try:
new_account = self.parent.app.db.add_account(name, secret_code, logo)
new_account = self.parent.app.db.add_account(
name, secret_code, logo)
self.parent.accounts_box.append(new_account)
self.close_window()
except Exception as e:
@ -210,9 +218,8 @@ class AddAccount(Gtk.Window):
def show_window(self):
if self.step == 1:
applications_choose_window = ApplicationChooserWindow(self)
applications_choose_window = ApplicationChooserWindow(self, self.parent)
applications_choose_window.show_window()
applications_choose_window.present()
self.step = 2
else:
self.secret_code.grab_focus_without_selecting()
@ -223,3 +230,186 @@ class AddAccount(Gtk.Window):
Close the window
"""
self.destroy()
class ApplicationRow(Gtk.ListBoxRow):
def __init__(self, name, image):
Gtk.ListBoxRow.__init__(self)
self.name = name
self.image = image
# Create the list row
self.create_row()
def get_name(self):
"""
Get the application label
:return: (str): application label
"""
return self.name
def get_icon_name(self):
return self.image
def get_icon(self):
return get_icon(self.image, 48)
def create_row(self):
"""
Create ListBoxRow
"""
self.builder = Gtk.Builder.new_from_resource("/org/gnome/Authenticator/application_row.ui")
self.builder.get_object("ApplicationLogo").set_from_pixbuf(self.get_icon())
self.builder.get_object("ApplicationName").set_text(self.name)
self.add(self.builder.get_object("MainBox"))
class ApplicationChooserWindow(Thread, GObject.GObject):
__gsignals__ = {
'db_updated': (GObject.SignalFlags.RUN_LAST, None, (bool,))
}
def __init__(self, parent, main_window):
self.parent = parent
Thread.__init__(self)
GObject.GObject.__init__(self)
self.nom = "applications-db-reader"
self.builder = Gtk.Builder.new_from_resource("/org/gnome/Authenticator/applications.ui")
self.builder.connect_signals({
"on_close" : self.close_window,
"on_key_press": self.on_key_press,
"on_apply" : self.select_application
})
self.window = self.builder.get_object("ApplicationsWindow")
self.window.set_transient_for(main_window)
self.listbox = self.builder.get_object("ApplicationsList")
self.generate_search_bar()
self.stack = self.builder.get_object("ApplicationsStack")
self.stack.set_visible_child(self.stack.get_child_by_name("loadingstack"))
self.builder.get_object("ApplicationListScrolled").add_with_viewport(self.listbox)
self.db = []
self.start()
def emit(self, *args):
GLib.idle_add(GObject.GObject.emit, self, *args)
def run(self):
# Load applications list using a Thread
self.builder.get_object("LoadingSpinner").start()
self.read_database()
self.add_apps()
self.emit("db_updated", True)
def generate_search_bar(self):
"""Generate the search bar."""
search_button = self.builder.get_object("SearchButton")
main_box = self.builder.get_object("MainBox")
self.search_bar = SearchBar(self.window, search_button, [self.listbox])
main_box.pack_start(self.search_bar, False, True, 0)
main_box.reorder_child(self.search_bar, 0)
def is_valid_app(self, app):
"""
Check if the application supports tfa
"""
if set(["tfa", "software"]).issubset(app.keys()):
return app["tfa"] and app["software"]
else:
return False
def on_key_press(self, label, key_event):
"""
Keyboard listener handling
"""
keyname = Gdk.keyval_name(key_event.keyval).lower()
if keyname == "escape":
if not self.search_bar.is_visible():
self.close_window()
return True
if keyname == "up" or keyname == "down":
dx = -1 if keyname == "up" else 1
index = self.listbox.get_selected_row().get_index()
index = (index + dx) % len(self.db)
selected_row = self.listbox.get_row_at_index(index)
self.listbox.select_row(selected_row)
return True
if keyname == "return":
self.select_application()
return True
return False
def do_db_updated(self, *args):
"""
Hide and stop the spinner and show the scrolled window
"""
self.builder.get_object("LoadingSpinner").stop()
self.stack.set_visible_child(self.stack.get_child_by_name("applicationsliststack"))
self.listbox.show_all()
logging.debug("UI updated")
def read_database(self):
"""
Read .yml database files provided by 2factorauth guys!
"""
db_dir = path.join(env.get("DATA_DIR"), "applications") + "/data/*.yml"
logging.debug("Database folder is {0}".format(db_dir))
db_files = glob(db_dir)
logging.debug("Reading database files started")
for db_file in db_files:
logging.debug("Reading database file {0}".format(db_file))
with open(db_file, 'r') as data:
try:
websites = yaml.load(data)["websites"]
for app in websites:
if self.is_valid_app(app):
self.db.append(app)
except yaml.YAMLError as error:
logging.error("Error loading yml file {0} : {1}".format(
db_file, str(error)))
except TypeError:
logging.error("Not a valid yml file {0}".format(db_file))
logging.debug("Reading database files finished")
def add_apps(self):
"""
Add database applications to the Gtk.ListBox
"""
self.db = sorted(self.db, key=lambda k: k['name'].lower())
logging.debug("Application list was ordered alphabetically")
for app in self.db:
img_path = app["img"]
app_name = app["name"]
self.listbox.add(ApplicationRow(app_name, img_path))
if len(self.db) != 0:
self.listbox.select_row(self.listbox.get_row_at_index(0))
def select_application(self, *args):
"""
Select a logo and return its path to the add application window
"""
selected_row = self.listbox.get_selected_row()
if selected_row:
img_path = selected_row.get_icon_name()
app_name = selected_row.get_name()
logging.debug("%s was selected" % app_name)
self.parent.update_logo(img_path)
self.parent.name_entry.set_text(app_name)
self.parent.show_window()
self.parent.present()
self.close_window()
def show_window(self):
self.window.show_all()
self.window.present()
def close_window(self, *args):
"""
Close the window
"""
logging.debug("Closing ApplicationChooserWindow")
self.window.destroy()

View file

@ -1,68 +0,0 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk
from Authenticator.utils import get_icon
from gettext import gettext as _
import logging
class ApplicationRow(Gtk.ListBoxRow):
def __init__(self, name, image):
Gtk.ListBoxRow.__init__(self)
self.name = name
self.image = image
# Create the list row
self.create_row()
def get_name(self):
"""
Get the application label
:return: (str): application label
"""
return self.name
def get_icon_name(self):
return self.image
def create_row(self):
"""
Create ListBoxRow
"""
self.get_style_context().add_class("application-list-row")
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
# Application logo
application_logo = get_icon(self.image, 48)
application_image = Gtk.Image(xalign=0)
application_image.set_from_pixbuf(application_logo)
hbox.pack_start(application_image, False, True, 6)
# Application name
application_name = Gtk.Label(xalign=0)
application_name.get_style_context().add_class("application-name")
application_name.set_text(self.name)
hbox.pack_start(application_name, True, True, 6)
vbox.pack_start(hbox, True, True, 6)
self.add(vbox)

View file

@ -1,248 +0,0 @@
# -*- coding: utf-8 -*-
"""
Copyright © 2016 Bilal Elmoussaoui <bil.elmoussaoui@gmail.com>
This file is part of Gnome-TwoFactorAuth.
Gnome-TwoFactorAuth is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TwoFactorAuth is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gnome-TwoFactorAuth. If not, see <http://www.gnu.org/licenses/>.
"""
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, GObject, Gio, Gdk, GLib
from Authenticator.widgets.search_bar import SearchBar
from Authenticator.widgets.application_row import ApplicationRow
from os import path, environ as env
from gettext import gettext as _
import yaml
from glob import glob
from threading import Thread
import logging
class ApplicationChooserWindow(Gtk.Window, Thread, GObject.GObject):
__gsignals__ = {
'db_updated': (GObject.SignalFlags.RUN_LAST, None, (bool,))
}
def __init__(self, window):
Thread.__init__(self)
GObject.GObject.__init__(self)
self.nom = "applications-db-reader"
Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL, modal=True,
destroy_with_parent=True)
self.parent = window
self.db = []
self.spinner = Gtk.Spinner()
self.search_button = Gtk.ToggleButton()
self.listbox = Gtk.ListBox()
self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.generate_window()
self.generate_search_bar()
self.generate_components()
self.generate_header_bar()
self.start()
def emit(self, *args):
GLib.idle_add(GObject.GObject.emit, self, *args)
def run(self):
# Load applications list using a Thread
self.read_database()
self.add_apps()
self.emit("db_updated", True)
def generate_window(self):
"""
Generate the main window
"""
self.connect("destroy", self.close_window)
self.resize(500, 650)
self.set_size_request(500, 650)
x, y = self.parent.parent.get_position()
if x and y:
self.move(x, y)
self.set_resizable(False)
self.set_transient_for(self.parent.parent)
self.connect("key_press_event", self.on_key_press)
self.add(self.main_box)
def generate_components(self):
"""
Generate window compenents
"""
box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
# Create a ScrolledWindow
self.scrolled_win = Gtk.ScrolledWindow()
self.scrolled_win.add_with_viewport(box_outer)
self.scrolled_win.hide()
self.main_box.pack_start(self.scrolled_win, True, True, 0)
self.listbox.get_style_context().add_class("applications-list")
self.listbox.set_adjustment()
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
box_outer.pack_start(self.listbox, True, True, 0)
self.spinner_box_outer = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL)
spinner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.spinner.start()
self.spinner.show()
spinner_box.pack_start(self.spinner, False, False, 6)
self.spinner_box_outer.pack_start(spinner_box, True, True, 6)
self.main_box.pack_start(self.spinner_box_outer, True, True, 0)
def generate_header_bar(self):
"""
Generate header bar box
"""
self.hb = Gtk.HeaderBar()
self.hb.props.title = _("Select an application")
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
cancel_button = Gtk.Button.new_with_label(_("Cancel"))
cancel_button.connect("clicked", self.close_window)
left_box.add(cancel_button)
search_icon = Gio.ThemedIcon(name="system-search-symbolic")
search_image = Gtk.Image.new_from_gicon(
search_icon, Gtk.IconSize.BUTTON)
self.search_button.set_tooltip_text(_("Search"))
self.search_button.set_image(search_image)
next_button = Gtk.Button.new_with_label(_("Next"))
next_button.get_style_context().add_class("suggested-action")
next_button.connect("clicked", self.select_application)
right_box.pack_start(self.search_button, False, False, 6)
right_box.pack_start(next_button, False, False, 6)
self.hb.pack_end(right_box)
self.hb.pack_start(left_box)
self.set_titlebar(self.hb)
def generate_search_bar(self):
"""
Generate the search bar
"""
self.search_bar = SearchBar(self, self.search_button, [self.listbox])
self.main_box.pack_start(self.search_bar, False, True, 0)
def is_valid_app(self, app):
"""
Check if the application supports tfa
"""
if set(["tfa", "software"]).issubset(app.keys()):
return app["tfa"] and app["software"]
else:
return False
def on_key_press(self, label, key_event):
"""
Keyboard listener handling
"""
keyname = Gdk.keyval_name(key_event.keyval).lower()
if keyname == "escape":
if not self.search_bar.is_visible():
self.close_window()
return True
if keyname == "up" or keyname == "down":
dx = -1 if keyname == "up" else 1
index = self.listbox.get_selected_row().get_index()
index = (index + dx) % len(self.db)
selected_row = self.listbox.get_row_at_index(index)
self.listbox.select_row(selected_row)
return True
if keyname == "return":
self.select_application()
return True
return False
def do_db_updated(self, *args):
"""
Hide and stop the spinner and show the scrolled window
"""
self.spinner.stop()
self.spinner_box_outer.hide()
self.scrolled_win.show()
self.listbox.hide()
if len(self.listbox.get_children()) != 0:
self.listbox.show_all()
logging.debug("UI updated")
def read_database(self):
"""
Read .yml database files provided by 2factorauth guys!
"""
db_dir = path.join(env.get("DATA_DIR"), "applications") + "/data/*.yml"
logging.debug("Database folder is {0}".format(db_dir))
db_files = glob(db_dir)
logging.debug("Reading database files started")
for db_file in db_files:
logging.debug("Reading database file {0}".format(db_file))
with open(db_file, 'r') as data:
try:
websites = yaml.load(data)["websites"]
for app in websites:
if self.is_valid_app(app):
self.db.append(app)
except yaml.YAMLError as error:
logging.error("Error loading yml file {0} : {1}".format(db_file, str(error)))
except TypeError:
logging.error("Not a valid yml file {0}".format(db_file))
logging.debug("Reading database files finished")
def add_apps(self):
"""
Add database applications to the Gtk.ListBox
"""
self.db = sorted(self.db, key=lambda k: k['name'].lower())
logging.debug("Application list was ordered alphabetically")
for app in self.db:
img_path = app["img"]
app_name = app["name"]
self.listbox.add(ApplicationRow(app_name, img_path))
if len(self.db) != 0:
self.listbox.select_row(self.listbox.get_row_at_index(0))
def select_application(self, *args):
"""
Select a logo and return its path to the add application window
"""
selected_row = self.listbox.get_selected_row()
if selected_row:
img_path = selected_row.get_icon_name()
app_name = selected_row.get_name()
logging.debug("%s was selected" % app_name)
self.parent.update_logo(img_path)
self.parent.name_entry.set_text(app_name)
self.parent.show_window()
self.parent.present()
self.close_window()
def show_window(self):
self.show_all()
def close_window(self, *args):
"""
Close the window
"""
logging.debug("Closing ApplicationChooserWindow")
self.destroy()

View file

@ -20,42 +20,33 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk
from Authenticator.models.settings import SettingsReader
from Authenticator.const import settings
from gettext import gettext as _
from hashlib import sha256
import logging
class PasswordWindow(Gtk.Window):
class PasswordWindow:
def __init__(self, window):
self.parent = window
self.cfg = SettingsReader()
self.hb = Gtk.HeaderBar()
self.apply_button = Gtk.Button.new_with_label(_("Apply"))
self.new_entry = Gtk.Entry()
self.new2_entry = Gtk.Entry()
self.old_entry = Gtk.Entry()
self.generate_window()
self.generate_components()
self.generate_header_bar()
def generate_window(self):
Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL, title=_("Change password"),
modal=True, destroy_with_parent=True)
self.connect("delete-event", self.close_window)
self.resize(300, 100)
self.set_border_width(18)
self.set_size_request(300, 100)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_resizable(False)
self.set_transient_for(self.parent)
self.connect("key_press_event", self.on_key_press)
def __init__(self, parent):
self.builder = Gtk.Builder.new_from_resource("/org/gnome/Authenticator/change_password.ui")
self.builder.connect_signals({
"on_quit" : self.close_window,
"on_type" : self.__on_type_password,
"on_apply": self.__update_password
})
self.window = self.builder.get_object("ChangePasswordWindow")
self.window.set_transient_for(parent)
self.old_entry = self.builder.get_object("OldPasswordEntry")
self.new_entry = self.builder.get_object("NewPasswordEntry")
self.apply_button = self.builder.get_object("ApplyButton")
self.repeat_entry = self.builder.get_object("RepeatPasswordEntry")
if not settings.is_password_set():
self.builder.get_object("PasswordGrid").remove_row(0)
self.window.connect("key_press_event", self.on_key_press)
def show_window(self):
self.show_all()
self.window.show_all()
def on_key_press(self, key, key_event):
"""
@ -64,118 +55,50 @@ class PasswordWindow(Gtk.Window):
if Gdk.keyval_name(key_event.keyval) == "Escape":
self.close_window()
def generate_components(self):
"""
Generate window components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
if len(self.cfg.read("password", "login")) != 0:
box_old = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
old_label = Gtk.Label()
old_label.set_text(_("Old password"))
self.old_entry.connect("changed", self.on_type_password)
self.old_entry.set_visibility(False)
box_old.pack_end(self.old_entry, False, True, 0)
box_old.pack_end(old_label, False, True, 0)
box.add(box_old)
box_new = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
new_label = Gtk.Label()
new_label.set_text(_("New password"))
self.new_entry.connect("changed", self.on_type_password)
self.new_entry.set_visibility(False)
box_new.pack_end(self.new_entry, False, True, 0)
box_new.pack_end(new_label, False, True, 0)
box_new2 = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
new2_label = Gtk.Label()
new2_label.set_text(_("Repeat new password"))
self.new2_entry.connect("changed", self.on_type_password)
self.new2_entry.set_visibility(False)
box_new2.pack_end(self.new2_entry, False, True, 0)
box_new2.pack_end(new2_label, False, True, 0)
box.add(box_new)
box.add(box_new2)
main_box.pack_start(box, False, True, 6)
self.add(main_box)
def update_password(self, *args):
def __update_password(self, *args):
"""
Update user password
"""
password = sha256(
self.new_entry.get_text().encode("utf-8")).hexdigest()
self.cfg.update("password", password, "login")
settings.set_password(self.new_entry.get_text())
logging.debug("Password changed successfully")
self.close_window()
def on_type_password(self, entry):
def __on_type_password(self, entry):
"""
Validate the old & new password
"""
pwd = self.cfg.read("password", "login")
old_is_ok = True
if self.new_entry.get_text() != self.new2_entry.get_text():
self.new_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
"dialog-error-symbolic")
self.new2_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
"dialog-error-symbolic")
are_diff = True
elif len(self.new_entry.get_text()) == 0:
are_diff = True
elif len(self.new_entry.get_text()) == 0:
# Check the new typed password
if (self.new_entry.get_text() != self.repeat_entry.get_text()
or len(self.new_entry.get_text()) == 0
or len(self.repeat_entry.get_text()) == 0):
are_diff = True
else:
are_diff = False
if len(pwd) != 0:
if sha256(self.old_entry.get_text().encode('utf-8')).hexdigest() != pwd:
self.old_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
"dialog-error-symbolic")
old_is_ok = False
# Check if the old password is set
if settings.is_password_set():
if not settings.compare_password(self.old_entry.get_text()):
old_is_valid = False
else:
old_is_ok = True
if old_is_ok:
self.old_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
if not are_diff:
self.new_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
self.new2_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
self.apply_button.set_sensitive(not are_diff and old_is_ok)
old_is_valid = True
self.__set_entry_status_icon(self.old_entry, old_is_valid)
else:
old_is_valid = True
def generate_header_bar(self):
"""
Generate header bar box
"""
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.__set_entry_status_icon(self.new_entry, not are_diff)
self.__set_entry_status_icon(self.repeat_entry, not are_diff)
self.apply_button.set_sensitive(not are_diff and old_is_valid)
cancel_button = Gtk.Button.new_with_label(_("Cancel"))
cancel_button.connect("clicked", self.close_window)
cancel_button.get_style_context().add_class("destructive-action")
left_box.add(cancel_button)
self.apply_button.get_style_context().add_class("suggested-action")
self.apply_button.connect("clicked", self.update_password)
self.apply_button.set_sensitive(False)
right_box.add(self.apply_button)
self.hb.pack_start(left_box)
self.hb.pack_end(right_box)
self.set_titlebar(self.hb)
def __set_entry_status_icon(self, entry, is_valid=False):
# Private function to change the Gtk.Entry secondary icon
if is_valid:
icon = None
else:
icon = "dialog-error-symbolic"
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
def close_window(self, *args):
"""
Close the window
"""
logging.debug("Closing PasswordWindow")
self.destroy()
self.window.destroy()

View file

@ -20,6 +20,7 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio
from Authenticator.const import settings
from Authenticator.utils import show_app_menu
import logging
from gettext import gettext as _
@ -77,7 +78,6 @@ class HeaderBar(Gtk.HeaderBar, Observer):
self.lock_button.set_tooltip_text(_("Lock the Application"))
self.lock_button.set_image(lock_image)
left_box.add(self.remove_button)
left_box.add(self.add_button)
left_box.add(self.lock_button)
@ -85,7 +85,7 @@ class HeaderBar(Gtk.HeaderBar, Observer):
def generate_right_box(self):
count = self.app.db.count()
is_grid = self.app.cfg.read("view-mode", "preferences").lower() == "grid"
is_grid = settings.get_view_mode() == "grid"
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
select_icon = Gio.ThemedIcon(name="object-select-symbolic")
select_image = Gtk.Image.new_from_gicon(
@ -155,7 +155,7 @@ class HeaderBar(Gtk.HeaderBar, Observer):
self.select_button.set_no_show_all(not counter > 0)
self.search_button.set_no_show_all(not counter > 0)
self.add_button.set_no_show_all(False)
if self.app.cfg.read("state", "login"):
if settings.get_is_locked():
self.lock_button.set_visible(True)
self.lock_button.set_no_show_all(False)
else:
@ -163,13 +163,13 @@ class HeaderBar(Gtk.HeaderBar, Observer):
self.lock_button.set_no_show_all(True)
def toggle_view_mode(self, *args):
is_grid = self.app.cfg.read("view-mode", "preferences").lower() == "grid"
is_grid = settings.get_view_mode() == "grid"
if not is_grid:
view_mode = "grid"
self.app.cfg.update("view-mode", "grid" , "preferences")
settings.set_view_mode("grid")
else:
view_mode = "list"
self.app.cfg.update("view-mode", "list" , "preferences")
settings.set_view_mode("list")
self.set_toggle_view_mode_icon(not is_grid)
self.window.emit("view_mode_changed", view_mode)
@ -180,12 +180,13 @@ class HeaderBar(Gtk.HeaderBar, Observer):
else:
view_mode_icon = Gio.ThemedIcon(name="view-list-symbolic")
self.view_mode_button.set_tooltip_text(_("List mode"))
view_mode_image = Gtk.Image.new_from_gicon(view_mode_icon, Gtk.IconSize.BUTTON)
view_mode_image = Gtk.Image.new_from_gicon(
view_mode_icon, Gtk.IconSize.BUTTON)
self.view_mode_button.set_image(view_mode_image)
def toggle_select_mode(self):
is_select_mode = self.window.is_select_mode
pass_enabled = self.app.cfg.read("state", "login")
pass_enabled = settings.get_is_locked()
self.remove_button.set_visible(is_select_mode)
self.cancel_button.set_visible(is_select_mode)
@ -209,18 +210,20 @@ class HeaderBar(Gtk.HeaderBar, Observer):
self.settings_button.set_visible(visible)
self.settings_button.set_no_show_all(not visible)
def refresh(self):
is_locked = self.app.locked
pass_enabled = self.app.cfg.read("state", "login")
is_locked = settings.get_is_locked()
pass_enabled = settings.get_is_locked()
can_be_locked = not is_locked and pass_enabled
count = self.app.db.count()
self.view_mode_button.set_visible(not count == 0 and not is_locked)
self.select_button.set_visible(not count == 0 and not is_locked)
self.search_button.set_visible(not count == 0 and not is_locked)
self.view_mode_button.set_no_show_all(not (not count == 0 and not is_locked))
self.select_button.set_no_show_all(not(not count == 0 and not is_locked))
self.search_button.set_no_show_all(not(not count == 0 and not is_locked))
self.view_mode_button.set_no_show_all(
not (not count == 0 and not is_locked))
self.select_button.set_no_show_all(
not(not count == 0 and not is_locked))
self.search_button.set_no_show_all(
not(not count == 0 and not is_locked))
self.lock_button.set_visible(can_be_locked)
self.add_button.set_visible(not is_locked)
self.lock_button.set_no_show_all(not can_be_locked)

View file

@ -3,10 +3,12 @@ import logging
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib
from Authenticator.models.observer import Observer
class InAppNotification(Gtk.Revealer):
class InAppNotification(Gtk.Revealer, Observer):
timer = 0
killed = False
def __init__(self, message="", undo_action=None, timeout=5):
Gtk.Revealer.__init__(self)
@ -50,9 +52,11 @@ class InAppNotification(Gtk.Revealer):
def hide(self):
self.set_reveal_child(False)
def update(self, message, undo_action=None):
def set_message(self, message):
self.message_label.set_text(message)
self.timer = 0
def set_undo_action(self, undo_action):
if undo_action:
if not self.undo_button:
self.undo_button = self.infobar.add_button(
@ -66,6 +70,11 @@ class InAppNotification(Gtk.Revealer):
self.undo_button.set_visible(False)
self.undo_button.set_no_show_all(True)
def update(self, *args, **kwargs):
is_alive = kwargs.pop("alive", None)
if not is_alive:
self.killed = True
def response(self, infobar, response_id):
if response_id == Gtk.ResponseType.CLOSE:
self.hide()
@ -73,10 +82,11 @@ class InAppNotification(Gtk.Revealer):
self.undo_action()
def update_timer(self):
if self.get_reveal_child():
if self.get_reveal_child() and not self.killed:
if self.timer == self.timeout:
self.hide()
self.timer = 0
else:
self.timer += 1
return True
return True
return False

View file

@ -23,48 +23,42 @@ from gi.repository import Gtk, Gdk
import logging
from hashlib import sha256
from gettext import gettext as _
from Authenticator.const import settings
from Authenticator.models.observer import Observer
class LoginWindow(Gtk.Box, Observer):
password_entry = None
unlock_button = None
def __init__(self, application, window):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.app = application
class LoginWindow(Gtk.Box, Observer):
def __init__(self, window):
self.window = window
self.password_entry = Gtk.Entry()
self.unlock_button = Gtk.Button()
self.generate()
self.window.connect("key-press-event", self.__on_key_press)
def generate(self):
password_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.password_entry.set_visibility(False)
self.password_entry.set_placeholder_text(_("Enter your password"))
password_box.pack_start(self.password_entry, False, False, 6)
self.unlock_button.set_label(_("Unlock"))
self.unlock_button.connect("clicked", self.on_unlock)
password_box.pack_start(self.unlock_button, False, False, 6)
self.pack_start(password_box, True, False, 6)
Gtk.Box.__init__(self)
self.builder = Gtk.Builder()
self.builder.connect_signals({
"on_unlock" : self.on_unlock
})
self.builder.add_from_resource('/org/gnome/Authenticator/login.ui')
login_window = self.builder.get_object("loginWindow")
self.pack_start(login_window, True, False, 0)
def on_unlock(self, *args):
"""
Password check and unlock
"""
typed_pass = self.password_entry.get_text()
password_entry = self.builder.get_object("passwordEntry")
typed_pass = password_entry.get_text()
ecrypted_pass = sha256(typed_pass.encode("utf-8")).hexdigest()
login_pass = self.app.cfg.read("password", "login")
if ecrypted_pass == login_pass or login_pass == typed_pass == "":
self.password_entry.set_icon_from_icon_name(
if (settings.compare_password(typed_pass)
or settings.get_password() == typed_pass == ""):
password_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
self.toggle_lock()
self.password_entry.set_text("")
password_entry.set_text("")
else:
self.password_entry.set_icon_from_icon_name(
password_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, "dialog-error-symbolic")
def __on_key_press(self, widget, event):
@ -74,7 +68,7 @@ class LoginWindow(Gtk.Box, Observer):
self.on_unlock()
return True
else:
pass_enabled = self.app.cfg.read("state", "login")
pass_enabled = settings.get_can_be_locked()
if keyname == "l" and pass_enabled:
if event.state & Gdk.ModifierType.CONTROL_MASK:
self.toggle_lock()
@ -86,12 +80,12 @@ class LoginWindow(Gtk.Box, Observer):
"""
Lock/unlock the application
"""
pass_enabled = self.app.cfg.read("state", "login")
pass_enabled = settings.get_can_be_locked()
if pass_enabled:
self.app.locked = not self.app.locked
settings.set_is_locked(not settings.get_is_locked())
self.window.counter = 0
if self.app.locked:
self.focus()
if settings.get_is_locked():
self.self.password_entry.grab_focus_without_selecting()
self.window.emit("locked", True)
else:
self.window.emit("unlocked", True)
@ -100,19 +94,10 @@ class LoginWindow(Gtk.Box, Observer):
is_locked = kwargs.pop("locked", None)
is_unlocked = kwargs.pop("unlocked", None)
if is_locked:
self.show()
self.set_visible(True)
self.set_no_show_all(False)
settings.set_is_locked(True)
elif is_unlocked:
self.hide()
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def focus(self):
self.password_entry.grab_focus_without_selecting()
settings.set_is_locked(False)
self.set_visible(False)
self.set_no_show_all(True)

View file

@ -24,6 +24,7 @@ import logging
from gettext import gettext as _
from Authenticator.models.observer import Observer
class NoAccountWindow(Gtk.Box, Observer):
def __init__(self):
@ -46,19 +47,8 @@ class NoAccountWindow(Gtk.Box, Observer):
locked = kwargs.pop("locked", None)
counter = kwargs.pop("counter", None)
if counter != 0 or locked:
self.hide()
self.set_visible(False)
self.set_no_show_all(True)
elif unlocked or counter == 0:
self.show()
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def is_visible(self):
return self.get_visible()
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
self.set_visible(True)
self.set_no_show_all(False)

View file

@ -21,6 +21,7 @@ from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk
import logging
from Authenticator.const import settings
class SearchBar(Gtk.Revealer):
@ -61,8 +62,6 @@ class SearchBar(Gtk.Revealer):
"""
Filter function, used to check if the entered data exists on the application ListBox
"""
if isinstance(row, Gtk.FlowBoxChild):
row = row.get_children()[0]
app_label = row.get_name()
data = data.lower()
if len(data) > 0:
@ -79,7 +78,7 @@ class SearchBar(Gtk.Revealer):
else:
self.focus()
if not "is_locked" in dir(self.window) or not self.window.is_locked():
if not settings.get_is_locked():
if keyname == "backspace":
if self.is_empty() and self.is_visible():
self.search_button.set_active(False)

View file

@ -20,166 +20,104 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk
from Authenticator.models.settings import SettingsReader
from Authenticator.const import settings
from Authenticator.widgets.change_password import PasswordWindow
from gettext import gettext as _
import logging
class SettingsWindow(Gtk.Window):
class SettingsWindow:
def __init__(self, parent):
self.parent = parent
self.cfg = SettingsReader()
self.auto_lock_time = Gtk.SpinButton()
self.enable_switch = Gtk.CheckButton()
self.auto_lock_switch = Gtk.CheckButton()
self.password_button = Gtk.Button()
self.hb = Gtk.HeaderBar()
self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.generate_window()
self.generate_components()
def generate_window(self):
Gtk.Window.__init__(self, title=_("Settings"), type=Gtk.WindowType.TOPLEVEL,
destroy_with_parent=True, modal=True)
self.connect("delete-event", self.close_window)
self.resize(400, 300)
self.set_size_request(400, 300)
self.set_border_width(18)
self.set_resizable(False)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_transient_for(self.parent)
self.connect("key_press_event", self.on_key_press)
self.hb.set_show_close_button(True)
self.set_titlebar(self.hb)
self.add(self.main_box)
self.builder = Gtk.Builder.new_from_resource('/org/gnome/Authenticator/settings.ui')
self.builder.connect_signals({
"on_change_password" : self.__new_password_window,
"on_activate_auto_lock" : self.__on_auto_lock_activated,
"on_change_auto_lock_time" : self.__on_auto_lock_time_changed,
"on_activate_password" : self.__on_password_activated,
"on_key_press": self.__on_key_press,
"on_activate_night_mode": self.__on_night_mode_activated,
"on_close_window": self.close_window
})
self.window = self.builder.get_object("SettingsWindow")
self.window.set_transient_for(self.parent)
logging.debug("Settings Window created")
self.auto_lock_check = self.builder.get_object("AutoLockCheck")
self.auto_lock_spin = self.builder.get_object("AutoLockSpin")
self.night_mode_check = self.builder.get_object("NightModeCheck")
self.password_check = self.builder.get_object("PasswordCheck")
self.password_button = self.builder.get_object("PasswordButton")
# Restore settings
_can_be_locked = settings.get_can_be_locked()
_auto_lock_status = settings.get_auto_lock_status()
_auto_lock_time = settings.get_auto_lock_time()
_night_mode = settings.get_is_night_mode()
self.night_mode_check.set_active(_night_mode)
self.password_check.set_active(_can_be_locked)
self.password_button.set_sensitive(_can_be_locked)
self.auto_lock_check.set_sensitive(_can_be_locked)
self.auto_lock_check.set_active(_can_be_locked)
self.auto_lock_spin.set_sensitive(_auto_lock_status)
self.auto_lock_spin.set_value(_auto_lock_time)
def show_window(self):
self.show_all()
self.window.show_all()
self.window.present()
def on_key_press(self, key, key_event):
def __on_key_press(self, key, key_event):
"""
Keyboard Listener handler
"""
if Gdk.keyval_name(key_event.keyval) == "Escape":
if Gdk.keyval_name(key_event.keyval).lower() == "escape":
self.close_window()
def generate_components(self):
"""
Generate all the components
"""
self.stack = Gtk.Stack()
self.stack.set_vexpand(True)
self.stack.set_hexpand(True)
self.stack.set_transition_type(
Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.stack.set_transition_duration(1000)
stack_switcher = Gtk.StackSwitcher()
stack_switcher.set_stack(self.stack)
self.hb.set_custom_title(stack_switcher)
behavior_settings = self.generate_behavior_settings()
account_settings = self.generate_account_settings()
self.stack.add_titled(behavior_settings, "behavior", _("Behavior"))
self.stack.add_titled(account_settings, "account", _("Account"))
self.main_box.add(self.stack)
def generate_account_settings(self):
"""
Create a box with login settings components
:return (Gtk.Box): Box contains all the components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
lock_enabled = bool(self.cfg.read("state", "login"))
self.enable_switch.set_active(lock_enabled)
self.enable_switch.connect("toggled", self.on_switch_activated)
password_label = Gtk.Label()
password_label.set_label(_("Password protection"))
self.password_button.get_style_context().add_class("flat")
self.password_button.get_style_context().add_class("text-button")
self.password_button.set_label("******")
self.password_button.connect("clicked", self.new_password_window)
self.password_button.set_sensitive(lock_enabled)
password_box.pack_start(self.enable_switch, False, True, 6)
password_box.pack_start(password_label, False, True, 6)
password_box.pack_start(self.password_button, False, True, 6)
main_box.pack_start(password_box, False, True, 6)
return main_box
def generate_behavior_settings(self):
"""
Create a box with user settings components
:return (Gtk.Box): Box contains all the components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
is_auto_lock_active = bool(self.cfg.read("auto-lock", "preferences"))
auto_lock_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
auto_lock_label = Gtk.Label().new(_("Auto-lock the application (m):"))
self.auto_lock_switch.set_sensitive(self.cfg.read("state", "login"))
self.auto_lock_switch.set_active(is_auto_lock_active)
self.auto_lock_switch.connect("toggled", self.on_auto_lock_activated)
default_value = self.cfg.read("auto-lock-time", "preferences")
if default_value < 1 or default_value > 10:
default_value = 3
adjustment = Gtk.Adjustment(value=default_value, lower=1, upper=10,
step_increment=1, page_increment=1, page_size=0)
self.auto_lock_time.connect(
"value-changed", self.on_auto_lock_time_changed)
self.auto_lock_time.set_adjustment(adjustment)
self.auto_lock_time.set_sensitive(is_auto_lock_active)
self.auto_lock_time.set_value(default_value)
auto_lock_box.pack_start(self.auto_lock_switch, False, True, 6)
auto_lock_box.pack_start(auto_lock_label, False, True, 6)
auto_lock_box.pack_start(self.auto_lock_time, False, True, 12)
main_box.pack_start(auto_lock_box, False, True, 6)
return main_box
def new_password_window(self, *args):
def __new_password_window(self, *args):
"""
Show a new password window
"""
pass_window = PasswordWindow(self)
pass_window = PasswordWindow(self.window)
pass_window.show_window()
def on_auto_lock_time_changed(self, spin_button):
def __on_auto_lock_time_changed(self, spin_button):
"""
Update auto lock time
"""
self.cfg.update("auto-lock-time",
spin_button.get_value_as_int(), "preferences")
settings.set_auto_lock_time(spin_button.get_value_as_int())
logging.info("Auto lock time updated")
def on_switch_activated(self, switch, *args):
def __on_night_mode_activated(self, checkbutton, *args):
checkbutton_status = checkbutton.get_active()
settings.set_is_night_mode(checkbutton_status)
Gtk.Settings.get_default().set_property(
"gtk-application-prefer-dark-theme", checkbutton_status)
def __on_password_activated(self, checkbutton, *args):
"""
Update password state : enabled/disabled
"""
self.password_button.set_sensitive(switch.get_active())
self.cfg.update("state", switch.get_active(), "login")
if switch.get_active():
password = self.cfg.read("password", "login")
if len(password) == 0:
self.new_password_window()
self.auto_lock_switch.set_sensitive(switch.get_active())
checkbutton_status = checkbutton.get_active()
self.password_button.set_sensitive(checkbutton_status)
settings.set_is_locked(checkbutton_status)
if checkbutton_status and not settings.is_password_set():
self.__new_password_window()
self.auto_lock_check.set_sensitive(checkbutton_status)
logging.info("Password enabled/disabled")
self.parent.refresh_window()
self.parent.emit("changed", True)
def on_auto_lock_activated(self, switch, *args):
def __on_auto_lock_activated(self, checkbutton, *args):
"""
Update auto-lock state : enabled/disabled
"""
self.auto_lock_time.set_sensitive(switch.get_active())
self.cfg.update("auto-lock", switch.get_active(), "preferences")
checkbutton_status = checkbutton.get_active()
self.auto_lock_spin.set_sensitive(checkbutton_status)
settings.set_auto_lock_status(checkbutton_status)
logging.info("Auto lock state updated")
def close_window(self, *args):
@ -187,4 +125,4 @@ class SettingsWindow(Gtk.Window):
Close the window
"""
logging.debug("SettingsWindow closed")
self.destroy()
self.window.destroy()

View file

@ -20,6 +20,7 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
from Authenticator.const import settings
from Authenticator.widgets.add_account import AddAccount
from Authenticator.widgets.accounts_window import AccountsWindow
from Authenticator.widgets.login_window import LoginWindow
@ -34,12 +35,12 @@ import logging
class Window(Gtk.ApplicationWindow, GObject.GObject):
__gsignals__ = {
'changed': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'locked': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'unlocked': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'view_mode_changed': (GObject.SignalFlags.RUN_LAST, None, (str,))
'changed': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'locked': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'unlocked': (GObject.SignalFlags.RUN_LAST, None, (bool,)),
'view_mode_changed': (GObject.SignalFlags.RUN_LAST, None, (str,))
}
counter = 0
counter = 1
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
is_select_mode = False
@ -51,7 +52,7 @@ class Window(Gtk.ApplicationWindow, GObject.GObject):
self.generate_accounts_box()
self.generate_no_accounts_box()
self.generate_login_box()
if self.app.locked:
if settings.get_can_be_locked():
self.emit("locked", True)
else:
self.emit("unlocked", True)
@ -72,6 +73,7 @@ class Window(Gtk.ApplicationWindow, GObject.GObject):
self.connect("key_press_event", self.on_key_press)
self.connect("delete-event", lambda x, y: self.app.on_quit())
self.notification = InAppNotification()
self.observable.register(self.notification)
self.main_box.pack_start(self.notification, False, False, 0)
self.add(self.main_box)
@ -80,8 +82,7 @@ class Window(Gtk.ApplicationWindow, GObject.GObject):
Keyboard Listener handling
"""
keyname = Gdk.keyval_name(key_event.keyval).lower()
if not self.is_locked():
if not settings.get_is_locked():
if not self.no_account_box.is_visible():
if keyname == "s" or keyname == "escape":
if key_event.state == Gdk.ModifierType.CONTROL_MASK or not self.hb.select_button.get_visible():
@ -96,18 +97,17 @@ class Window(Gtk.ApplicationWindow, GObject.GObject):
if key_event.state == Gdk.ModifierType.CONTROL_MASK:
self.hb.toggle_view_mode()
return True
return False
def refresh_counter(self):
"""
Add a value to the counter each 60 seconds
"""
if not self.app.locked:
if settings.get_is_locked():
self.counter += 1
if self.app.cfg.read("auto-lock", "preferences"):
if self.counter == self.app.cfg.read("auto-lock-time", "preferences") - 1:
self.counter = 0
if settings.get_auto_lock_status():
if self.counter == settings.get_auto_lock_time():
self.counter = 1
self.emit("locked", True)
return True
@ -115,9 +115,10 @@ class Window(Gtk.ApplicationWindow, GObject.GObject):
"""
Generate login form
"""
self.login_box = LoginWindow(self.app, self)
self.login_box = LoginWindow(self)
self.observable.register(self.login_box)
self.hb.lock_button.connect("clicked", lambda x : self.emit("locked", True))
self.hb.lock_button.connect(
"clicked", lambda x: self.emit("locked", True))
self.main_box.pack_start(self.login_box, True, False, 0)
def generate_accounts_box(self):
@ -125,8 +126,6 @@ class Window(Gtk.ApplicationWindow, GObject.GObject):
self.observable.register(self.accounts_box)
self.accounts_list = self.accounts_box.get_accounts_list()
self.accounts_grid = self.accounts_box.get_accounts_grid()
self.hb.remove_button.connect(
"clicked", self.accounts_list.remove_selected)
self.search_bar = self.accounts_box.get_search_bar()
self.main_box.pack_start(self.accounts_box, True, True, 0)
@ -171,6 +170,10 @@ class Window(Gtk.ApplicationWindow, GObject.GObject):
self.main_box.pack_start(self.no_account_box, True, False, 0)
def do_view_mode_changed(self, *args):
if args[0] == "list":
self.hb.remove_button.connect("clicked", self.accounts_list.remove_selected)
else:
self.hb.remove_button.connect("clicked", self.accounts_grid.remove_selected)
self.observable.update_observers(view_mode=args[0])
def do_changed(self, *args):
@ -197,27 +200,21 @@ class Window(Gtk.ApplicationWindow, GObject.GObject):
"""
Save window position
"""
pos_x, pos_y = self.get_position()
size_x, size_y = self.get_size()
self.app.cfg.update("position-x", pos_x, "preferences")
self.app.cfg.update("position-y", pos_y, "preferences")
self.app.cfg.update("size-x", size_x, "preferences")
self.app.cfg.update("size-y", size_y, "preferences")
settings.set_window_postion(self.get_position())
settings.set_window_size(self.get_size())
def move_latest_position(self):
"""
move the application window to the latest position
"""
x = self.app.cfg.read("position-x", "preferences")
y = self.app.cfg.read("position-y", "preferences")
if x != 0 and y != 0:
self.move(x, y)
position_x, position_y = settings.get_window_position()
if position_x != 0 and position_y != 0:
self.move(position_x, position_y)
else:
self.set_position(Gtk.WindowPosition.CENTER)
def use_latest_size(self):
x = self.app.cfg.read("size-x", "preferences")
y = self.app.cfg.read("size-y", "preferences")
self.resize(x, y)
self.props.width_request = 500
self.props.height_request = 650
width, height = settings.get_window_size()
default_width, default_height = settings.get_default_size()
self.resize(width, height)
self.set_size_request(default_width, default_height)

53
data/application_row.ui Normal file
View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="MainBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkImage" id="ApplicationLogo">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ApplicationName">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<style>
<class name="application-name"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<style>
<class name="application-list-row"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</interface>

149
data/applications.ui Normal file
View file

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkListBox" id="ApplicationsList">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<style>
<class name="applications-list"/>
</style>
</object>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">search-symbolic</property>
</object>
<object class="GtkWindow" id="ApplicationsWindow">
<property name="width_request">500</property>
<property name="height_request">650</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">500</property>
<property name="default_height">650</property>
<property name="destroy_with_parent">True</property>
<signal name="destroy" handler="on_close" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<child>
<object class="GtkStack" id="ApplicationsStack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="transition_duration">400</property>
<property name="transition_type">crossfade</property>
<child>
<object class="GtkBox" id="MainBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkScrolledWindow" id="ApplicationListScrolled">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">etched-in</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="name">applicationsliststack</property>
<property name="title" translatable="yes">page0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="LoadingGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="border_width">18</property>
<child>
<object class="GtkLabel" id="LoadingLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Loading data..</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkSpinner" id="LoadingSpinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="name">loadingstack</property>
<property name="title" translatable="yes">page1</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title">Select an application</property>
<child>
<object class="GtkButton" id="CancelButton">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_close" swapped="no"/>
<style>
<class name="destructive-action"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="NextButton">
<property name="label" translatable="yes">Next</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_apply" swapped="no"/>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="SearchButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Search</property>
<property name="image">image1</property>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

157
data/change_password.ui Normal file
View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="ChangePasswordWindow">
<property name="width_request">300</property>
<property name="height_request">100</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">18</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-always</property>
<property name="default_width">300</property>
<property name="default_height">100</property>
<property name="destroy_with_parent">True</property>
<property name="gravity">static</property>
<signal name="delete-event" handler="on_quit" swapped="no"/>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkGrid" id="PasswordGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkLabel" id="OldPasswordLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Old Password</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="NewPasswordLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">New Password</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="RepeatPasswordLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Repeat New Password</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="OldPasswordEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="input_purpose">password</property>
<signal name="changed" handler="on_type" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="NewPasswordEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="input_purpose">password</property>
<signal name="changed" handler="on_type" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="RepeatPasswordEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="input_purpose">password</property>
<signal name="changed" handler="on_type" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton" id="CancelButton">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_quit" swapped="no"/>
<style>
<class name="destructive-action"/>
</style>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ApplyButton">
<property name="label" translatable="yes">Apply</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_apply" swapped="no"/>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

41
data/login.ui Normal file
View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkGrid" id="loginWindow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="orientation">vertical</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkEntry" id="passwordEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">*</property>
<property name="placeholder_text" translatable="yes">Enter your password</property>
<property name="input_purpose">password</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="unlockButton">
<property name="label" translatable="yes">Unlock</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
</interface>

View file

@ -3,7 +3,12 @@
<gresource prefix="/org/gnome/Authenticator">
<file>org.gnome.Authenticator-post3.20.css</file>
<file>org.gnome.Authenticator-pre3.20.css</file>
<file preprocess="xml-stripblanks">about.ui</file>
<file preprocess="xml-stripblanks">about.ui</file>
<file preprocess="xml-stripblanks">application_row.ui</file>
<file preprocess="xml-stripblanks">applications.ui</file>
<file preprocess="xml-stripblanks">change_password.ui</file>
<file preprocess="xml-stripblanks">login.ui</file>
<file preprocess="xml-stripblanks">settings.ui</file>
<file preprocess="xml-stripblanks">shortcuts.ui</file>
</gresource>
</gresources>

View file

@ -1,13 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/org/gnome/Authenticator/preferences/" id="org.gnome.Authenticator.preferences" gettext-domain="Authenticator">
<key name="refresh-time" type="i">
<default>30</default>
<summary>Time to generate an other password</summary>
<description>
Time on seconds to generate a new password
</description>
</key>
<schema path="/org/gnome/Authenticator/" id="org.gnome.Authenticator" gettext-domain="Authenticator">
<key name="auto-lock" type="b">
<default>false</default>
<summary>Auto lock the application</summary>
@ -65,8 +58,13 @@
Enable/disable night mode within the application
</description>
</key>
</schema>
<schema path="/org/gnome/Authenticator/login/" id="org.gnome.Authenticator.login" gettext-domain="Authenticator">
<key name="locked" type="b">
<default>false</default>
<summary>Define wether the application is locked or not</summary>
<description>
This only works if the password protection is enabled
</description>
</key>
<key name="password" type="s">
<default>""</default>
<summary>Access code to user interface</summary>

163
data/settings.ui Normal file
View file

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="adjustment1">
<property name="lower">1</property>
<property name="upper">10</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkWindow" id="SettingsWindow">
<property name="width_request">400</property>
<property name="height_request">300</property>
<property name="can_focus">False</property>
<property name="border_width">18</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">400</property>
<property name="default_height">300</property>
<property name="destroy_with_parent">True</property>
<signal name="delete-event" handler="on_close_window" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<child>
<object class="GtkStack" id="stack1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="transition_duration">700</property>
<property name="transition_type">slide-left-right</property>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkCheckButton" id="AutoLockCheck">
<property name="label" translatable="yes">Auto lock the application (m) :</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_activate_auto_lock" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="AutoLockSpin">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="activates_default">True</property>
<property name="input_purpose">digits</property>
<property name="adjustment">adjustment1</property>
<property name="climb_rate">1</property>
<property name="numeric">True</property>
<signal name="value-changed" handler="on_change_auto_lock_time" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="NightModeCheck">
<property name="label" translatable="yes">Night Mode</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_activate_night_mode" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="name">behaviour</property>
<property name="title" translatable="yes">Behaviour</property>
</packing>
</child>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkCheckButton" id="PasswordCheck">
<property name="label" translatable="yes">Password protection</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_activate_password" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="PasswordButton">
<property name="label">******</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_change_password" swapped="no"/>
<style>
<class name="flat"/>
<class name="text-button"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="name">page1</property>
<property name="title" translatable="yes">Account</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkStackSwitcher">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="stack">stack1</property>
</object>
</child>
<child type="title">
<placeholder/>
</child>
</object>
</child>
</object>
</interface>