mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
beta 3 release , fixes #12
This commit is contained in:
parent
759e1c5b2c
commit
b88ec4365c
12 changed files with 413 additions and 334 deletions
|
@ -4,6 +4,8 @@ app_PYTHON = \
|
|||
add_account.py \
|
||||
confirmation.py \
|
||||
account_row.py \
|
||||
accounts_list.py \
|
||||
accounts_window.py \
|
||||
login_window.py \
|
||||
headerbar.py \
|
||||
applications_list.py \
|
||||
|
|
|
@ -4,6 +4,7 @@ from gi.repository import Gtk, Gdk, GLib
|
|||
from TwoFactorAuth.models.code import Code
|
||||
from TwoFactorAuth.models.settings import SettingsReader
|
||||
from TwoFactorAuth.models.database import Database
|
||||
from TwoFactorAuth.widgets.confirmation import ConfirmationMessage
|
||||
from TwoFactorAuth.utils import get_icon
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
@ -19,24 +20,25 @@ class AccountRow(Thread, Gtk.ListBoxRow):
|
|||
code_generated = True
|
||||
alive = True
|
||||
|
||||
def __init__(self, parent, uid, name, secret_code, logo):
|
||||
def __init__(self, parent, window, app):
|
||||
Thread.__init__(self)
|
||||
Gtk.ListBoxRow.__init__(self)
|
||||
# Read default values
|
||||
cfg = SettingsReader()
|
||||
self.counter_max = cfg.read("refresh-time", "preferences")
|
||||
self.counter = self.counter_max
|
||||
self.window = window
|
||||
self.parent = parent
|
||||
self.id = uid
|
||||
self.name = name
|
||||
self.secret_code = Database.fetch_secret_code(secret_code)
|
||||
self.id = app[0]
|
||||
self.name = app[1]
|
||||
self.secret_code = Database.fetch_secret_code(app[2])
|
||||
if self.secret_code:
|
||||
self.code = Code(self.secret_code)
|
||||
else:
|
||||
self.code_generated = False
|
||||
logging.error(
|
||||
"Could not read the secret code from, the keyring keys were reset manually")
|
||||
self.logo = logo
|
||||
self.logo = app[3]
|
||||
# Create needed widgets
|
||||
self.code_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
self.revealer = Gtk.Revealer()
|
||||
|
@ -47,6 +49,7 @@ class AccountRow(Thread, Gtk.ListBoxRow):
|
|||
# Create the list row
|
||||
self.create_row()
|
||||
self.start()
|
||||
self.window.connect("key-press-event", self.__on_key_press)
|
||||
GLib.timeout_add_seconds(1, self.refresh_listbox)
|
||||
|
||||
def get_id(self):
|
||||
|
@ -166,12 +169,11 @@ class AccountRow(Thread, Gtk.ListBoxRow):
|
|||
# Remove button
|
||||
remove_event = Gtk.EventBox()
|
||||
remove_button = Gtk.Image(xalign=0)
|
||||
remove_button.set_from_icon_name(
|
||||
"user-trash-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
|
||||
remove_button.set_from_icon_name("user-trash-symbolic",
|
||||
Gtk.IconSize.SMALL_TOOLBAR)
|
||||
remove_button.set_tooltip_text(_("Remove the account"))
|
||||
remove_event.add(remove_button)
|
||||
remove_event.connect("button-press-event",
|
||||
self.parent.remove_account)
|
||||
remove_event.connect("button-press-event", self.remove)
|
||||
h_box.pack_end(remove_event, False, True, 6)
|
||||
|
||||
self.timer_label.set_label(_("Expires in %s seconds") % self.counter)
|
||||
|
@ -195,7 +197,7 @@ class AccountRow(Thread, Gtk.ListBoxRow):
|
|||
self.toggle_code_box()
|
||||
|
||||
def run(self):
|
||||
while self.code_generated and self.parent.app.alive and self.alive:
|
||||
while self.code_generated and self.window.app.alive and self.alive:
|
||||
self.counter -= 1
|
||||
if self.counter == 0:
|
||||
self.counter = self.counter_max
|
||||
|
@ -207,8 +209,7 @@ class AccountRow(Thread, Gtk.ListBoxRow):
|
|||
sleep(1)
|
||||
|
||||
def refresh_listbox(self):
|
||||
self.parent.list_box.hide()
|
||||
self.parent.list_box.show_all()
|
||||
self.window.accounts_list.refresh()
|
||||
return self.code_generated
|
||||
|
||||
def regenerate_code(self):
|
||||
|
@ -229,5 +230,38 @@ class AccountRow(Thread, Gtk.ListBoxRow):
|
|||
label.set_text(_("Couldn't generate the secret code"))
|
||||
self.code_generated = False
|
||||
|
||||
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.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
|
||||
|
||||
def remove(self, *args):
|
||||
"""
|
||||
Remove an account
|
||||
"""
|
||||
message = _("Do you really want to remove this account?")
|
||||
confirmation = ConfirmationMessage(self.window, message)
|
||||
confirmation.show()
|
||||
if confirmation.get_confirmation():
|
||||
self.kill()
|
||||
self.window.accounts_list.remove(self)
|
||||
self.window.app.db.remove_by_id(self.get_id())
|
||||
confirmation.destroy()
|
||||
self.window.refresh_window()
|
||||
|
||||
def update_timer_label(self):
|
||||
self.timer_label.set_label(_("Expires in %s seconds") % self.counter)
|
||||
|
|
151
TwoFactorAuth/widgets/accounts_list.py
Normal file
151
TwoFactorAuth/widgets/accounts_list.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from gi import require_version
|
||||
require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
|
||||
from TwoFactorAuth.widgets.confirmation import ConfirmationMessage
|
||||
from TwoFactorAuth.widgets.account_row import AccountRow
|
||||
import logging
|
||||
from gettext import gettext as _
|
||||
from hashlib import sha256
|
||||
|
||||
|
||||
class AccountsList(Gtk.ListBox):
|
||||
scrolled_win = None
|
||||
selected_count = 0
|
||||
|
||||
def __init__(self, application, window):
|
||||
self.app = application
|
||||
self.window = window
|
||||
self.generate()
|
||||
self.window.connect("key-press-event", self.on_key_press)
|
||||
|
||||
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)
|
||||
|
||||
apps = self.app.db.fetch_apps()
|
||||
count = len(apps)
|
||||
|
||||
for app in apps:
|
||||
self.add(AccountRow(self, self.window, app))
|
||||
|
||||
if count != 0:
|
||||
self.select_row(self.get_row_at_index(0))
|
||||
self.show_all()
|
||||
|
||||
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 = self.app.db.count()
|
||||
dx = -1 if keyname == "up" else 1
|
||||
selected_row = self.get_selected_row()
|
||||
if selected_row is not None:
|
||||
index = selected_row.get_index()
|
||||
index = (index + dx)%count
|
||||
self.select_row(self.get_row_at_index(index))
|
||||
return True
|
||||
return False
|
||||
|
||||
def toggle_select_mode(self):
|
||||
pass_enabled = self.app.cfg.read("state", "login")
|
||||
is_select_mode = self.window.hb.is_on_select_mode()
|
||||
if is_select_mode:
|
||||
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
|
||||
else:
|
||||
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
|
||||
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()
|
||||
selected = checkbox.get_active()
|
||||
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")
|
||||
|
||||
checkbox.set_visible(not visible)
|
||||
checkbox.set_no_show_all(visible)
|
||||
|
||||
def append(self, app):
|
||||
"""
|
||||
Add an element to the ListBox
|
||||
"""
|
||||
app[2] = sha256(app[2].encode('utf-8')).hexdigest()
|
||||
self.add(AccountRow(self, self.window, app))
|
||||
self.show_all()
|
||||
|
||||
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.toggle_select_mode()
|
||||
self.window.refresh_window()
|
||||
|
||||
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)
|
||||
if is_visible:
|
||||
self.selected_count += 1
|
||||
else:
|
||||
self.unselect_row(listbox_row)
|
||||
if is_visible:
|
||||
self.selected_count -= 1
|
||||
self.window.hb.remove_button.set_sensitive(self.selected_count > 0)
|
||||
|
||||
def get_selected_row_id(self):
|
||||
selected_row = self.get_selected_row()
|
||||
if selected_row:
|
||||
return selected_row.get_id()
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_scrolled_win(self):
|
||||
return self.scrolled_win
|
||||
|
||||
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):
|
||||
self.scrolled_win.hide()
|
||||
self.scrolled_win.show_all()
|
55
TwoFactorAuth/widgets/accounts_window.py
Normal file
55
TwoFactorAuth/widgets/accounts_window.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
from gi import require_version
|
||||
require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
|
||||
from TwoFactorAuth.widgets.accounts_list import AccountsList
|
||||
from TwoFactorAuth.widgets.search_bar import SearchBar
|
||||
import logging
|
||||
from gettext import gettext as _
|
||||
from hashlib import sha256
|
||||
|
||||
class AccountsWindow(Gtk.Box):
|
||||
|
||||
def __init__(self, application, window):
|
||||
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
|
||||
self.app = application
|
||||
self.window = window
|
||||
self.generate()
|
||||
|
||||
def generate(self):
|
||||
self.generate_accounts_list()
|
||||
self.generate_search_bar()
|
||||
self.pack_start(self.search_bar, False, True, 0)
|
||||
self.pack_start(self.scrolled_win, True, True, 0)
|
||||
|
||||
def generate_accounts_list(self):
|
||||
"""
|
||||
Generate an account ListBox inside of a ScrolledWindow
|
||||
"""
|
||||
self.accounts_list = AccountsList(self.app, self.window)
|
||||
self.scrolled_win = self.accounts_list.get_scrolled_win()
|
||||
|
||||
def generate_search_bar(self):
|
||||
"""
|
||||
Generate search bar box and entry
|
||||
"""
|
||||
self.search_bar = SearchBar(self.accounts_list, self.window,
|
||||
self.window.hb.search_button)
|
||||
|
||||
def get_accounts_list(self):
|
||||
return self.accounts_list
|
||||
|
||||
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)
|
|
@ -13,7 +13,7 @@ class AddAccount(Gtk.Window):
|
|||
def __init__(self, window):
|
||||
self.parent = window
|
||||
|
||||
self.selected_image = None
|
||||
self.selected_logo = None
|
||||
self.step = 1
|
||||
self.logo_image = Gtk.Image(xalign=0)
|
||||
self.secret_code = Gtk.Entry()
|
||||
|
@ -109,7 +109,7 @@ class AddAccount(Gtk.Window):
|
|||
"""
|
||||
Update image logo
|
||||
"""
|
||||
self.selected_image = image
|
||||
self.selected_logo = image
|
||||
auth_icon = get_icon(image)
|
||||
self.logo_image.clear()
|
||||
self.logo_image.set_from_pixbuf(auth_icon)
|
||||
|
@ -132,15 +132,13 @@ class AddAccount(Gtk.Window):
|
|||
"""
|
||||
Add a new application to the database
|
||||
"""
|
||||
name_entry = self.name_entry.get_text()
|
||||
secret_entry = self.secret_code.get_text()
|
||||
image_entry = self.selected_image if self.selected_image else "image-missing"
|
||||
name = self.name_entry.get_text()
|
||||
secret_code = self.secret_code.get_text()
|
||||
logo = self.selected_logo if self.selected_logo else "image-missing"
|
||||
try:
|
||||
self.parent.app.db.add_account(name_entry, secret_entry,
|
||||
image_entry)
|
||||
self.parent.app.db.add_account(name, secret_code, logo)
|
||||
uid = self.parent.app.db.get_latest_id()
|
||||
self.parent.append_list_box(
|
||||
uid, name_entry, secret_entry, image_entry)
|
||||
self.parent.accounts_list.append([uid, name, secret_code, logo])
|
||||
self.parent.refresh_window()
|
||||
self.close_window()
|
||||
except Exception as e:
|
||||
|
|
|
@ -50,9 +50,9 @@ class ApplicationChooserWindow(Gtk.Window):
|
|||
box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
if len(self.logos) > 0:
|
||||
# Create a ScrolledWindow for installed applications
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
scrolled_win.add_with_viewport(box_outer)
|
||||
self.main_box.pack_start(scrolled_win, True, True, 0)
|
||||
self.scrolled_win = Gtk.ScrolledWindow()
|
||||
self.scrolled_win.add_with_viewport(box_outer)
|
||||
self.main_box.pack_start(self.scrolled_win, True, True, 0)
|
||||
|
||||
self.listbox.get_style_context().add_class("applications-list")
|
||||
self.listbox.set_adjustment()
|
||||
|
@ -66,6 +66,7 @@ class ApplicationChooserWindow(Gtk.Window):
|
|||
app_logo = get_icon(img_path)
|
||||
self.listbox.add(ApplicationRow(app_name, app_logo))
|
||||
i += 1
|
||||
self.listbox.select_row(self.listbox.get_row_at_index(0))
|
||||
|
||||
def generate_header_bar(self):
|
||||
"""
|
||||
|
@ -103,27 +104,32 @@ class ApplicationChooserWindow(Gtk.Window):
|
|||
"""
|
||||
Generate the search bar
|
||||
"""
|
||||
self.search_bar = SearchBar(self.listbox)
|
||||
self.search_button.connect("toggled", self.search_bar.toggle)
|
||||
|
||||
self.search_bar = SearchBar(self.listbox, self, self.search_button)
|
||||
self.main_box.pack_start(self.search_bar, False, True, 0)
|
||||
|
||||
def on_key_press(self, label, key_event):
|
||||
"""
|
||||
Keyboard listener handling
|
||||
"""
|
||||
key_pressed = Gdk.keyval_name(key_event.keyval).lower()
|
||||
if key_pressed == "escape":
|
||||
if self.search_bar.is_visible():
|
||||
self.search_bar.toggle()
|
||||
else:
|
||||
keyname = Gdk.keyval_name(key_event.keyval).lower()
|
||||
|
||||
if keyname == "escape":
|
||||
if not self.search_bar.is_visible():
|
||||
self.close_window()
|
||||
elif key_pressed == "f":
|
||||
if key_event.state == Gdk.ModifierType.CONTROL_MASK:
|
||||
self.search_button.set_active(
|
||||
not self.search_button.get_active())
|
||||
elif key_pressed == "return":
|
||||
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.logos)
|
||||
selected_row = self.listbox.get_row_at_index(index)
|
||||
self.listbox.select_row(selected_row)
|
||||
return True
|
||||
|
||||
if keyname == "return":
|
||||
self.select_logo()
|
||||
return True
|
||||
return False
|
||||
|
||||
def select_logo(self, *args):
|
||||
"""
|
||||
|
|
|
@ -141,7 +141,7 @@ class HeaderBar(Gtk.HeaderBar):
|
|||
def toggle_settings_button(self, visible):
|
||||
if not is_gnome():
|
||||
self.settings_button.set_visible(visible)
|
||||
self.lock_bsettings_buttonutton.set_no_show_all(not visible)
|
||||
self.settings_button.set_no_show_all(not visible)
|
||||
|
||||
def toggle_lock_button(self, visible):
|
||||
self.lock_button.set_visible(visible)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from gi import require_version
|
||||
require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
|
||||
from gi.repository import Gtk, Gdk
|
||||
import logging
|
||||
from hashlib import sha256
|
||||
from gettext import gettext as _
|
||||
|
@ -9,14 +9,16 @@ class LoginWindow(Gtk.Box):
|
|||
password_entry = None
|
||||
unlock_button = None
|
||||
|
||||
def __init__(self, application):
|
||||
self.app = application
|
||||
def __init__(self, application, window):
|
||||
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
|
||||
self.app = application
|
||||
self.window = window
|
||||
self.password_entry = Gtk.Entry()
|
||||
self.unlock_button = Gtk.Button()
|
||||
self.generate_box()
|
||||
self.generate()
|
||||
self.window.connect("key-press-event", self.__on_key_press)
|
||||
|
||||
def generate_box(self):
|
||||
def generate(self):
|
||||
password_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.password_entry.set_visibility(False)
|
||||
|
@ -45,15 +47,32 @@ class LoginWindow(Gtk.Box):
|
|||
self.password_entry.set_icon_from_icon_name(
|
||||
Gtk.EntryIconPosition.SECONDARY, "dialog-error-symbolic")
|
||||
|
||||
def toggle_lock(self):
|
||||
def __on_key_press(self, widget, event):
|
||||
keyname = Gdk.keyval_name(event.keyval).lower()
|
||||
if self.window.is_locked():
|
||||
if keyname == "return":
|
||||
self.on_unlock()
|
||||
return True
|
||||
else:
|
||||
pass_enabled = self.app.cfg.read("state", "login")
|
||||
if keyname == "l" and pass_enabled:
|
||||
if event.state & Gdk.ModifierType.CONTROL_MASK:
|
||||
self.toggle_lock()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def toggle_lock(self, *args):
|
||||
"""
|
||||
Lock/unlock the application
|
||||
"""
|
||||
self.app.locked = not self.app.locked
|
||||
if self.app.locked:
|
||||
self.focus()
|
||||
self.app.refresh_menu()
|
||||
self.app.win.refresh_window()
|
||||
pass_enabled = self.app.cfg.read("state", "login")
|
||||
if pass_enabled:
|
||||
self.app.locked = not self.app.locked
|
||||
if self.app.locked:
|
||||
self.focus()
|
||||
self.app.refresh_menu()
|
||||
self.app.win.refresh_window()
|
||||
|
||||
def toggle(self, visible):
|
||||
self.set_visible(visible)
|
||||
|
|
|
@ -9,9 +9,9 @@ class NoAccountWindow(Gtk.Box):
|
|||
def __init__(self):
|
||||
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL,
|
||||
spacing=6)
|
||||
self.generate_box()
|
||||
self.generate()
|
||||
|
||||
def generate_box(self):
|
||||
def generate(self):
|
||||
logo_image = Gtk.Image()
|
||||
logo_image.set_from_icon_name("dialog-information-symbolic",
|
||||
Gtk.IconSize.DIALOG)
|
||||
|
@ -25,6 +25,9 @@ class NoAccountWindow(Gtk.Box):
|
|||
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)
|
||||
|
||||
|
|
|
@ -1,41 +1,42 @@
|
|||
from gi import require_version
|
||||
require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio
|
||||
from gi.repository import Gtk, Gio, Gdk
|
||||
import logging
|
||||
|
||||
|
||||
class SearchBar(Gtk.Box):
|
||||
class SearchBar(Gtk.Revealer):
|
||||
|
||||
def __init__(self, list_accounts):
|
||||
self.search_entry = Gtk.Entry()
|
||||
self.list_accounts = list_accounts
|
||||
def __init__(self, listbox, window, search_button):
|
||||
self.search_entry = Gtk.SearchEntry()
|
||||
self.listbox = listbox
|
||||
self.search_button = search_button
|
||||
self.window = window
|
||||
self.generate()
|
||||
self.search_button.connect("toggled", self.toggle)
|
||||
self.window.connect("key-press-event", self.__on_key_press)
|
||||
|
||||
def generate(self):
|
||||
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
|
||||
self.revealer = Gtk.Revealer()
|
||||
Gtk.Revealer.__init__(self)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
self.search_entry.set_width_chars(28)
|
||||
self.search_entry.connect("changed", self.filter_applications)
|
||||
self.search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,
|
||||
"system-search-symbolic")
|
||||
self.search_entry.connect("search-changed", self.filter_applications)
|
||||
|
||||
box.pack_start(self.search_entry, True, False, 12)
|
||||
box.props.margin = 6
|
||||
self.revealer.add(box)
|
||||
self.revealer.set_reveal_child(False)
|
||||
self.pack_start(self.revealer, True, False, 0)
|
||||
|
||||
self.add(box)
|
||||
self.set_reveal_child(False)
|
||||
|
||||
def toggle(self, *args):
|
||||
if self.revealer.get_reveal_child():
|
||||
self.revealer.set_reveal_child(False)
|
||||
if self.is_visible():
|
||||
self.set_reveal_child(False)
|
||||
self.search_entry.set_text("")
|
||||
self.list_accounts.set_filter_func(lambda x, y, z: True,
|
||||
self.listbox.set_filter_func(lambda x, y, z: True,
|
||||
None, False)
|
||||
else:
|
||||
self.revealer.set_reveal_child(True)
|
||||
self.search_entry.grab_focus_without_selecting()
|
||||
self.set_reveal_child(True)
|
||||
self.focus()
|
||||
|
||||
def filter_func(self, row, data, notify_destroy):
|
||||
"""
|
||||
|
@ -48,23 +49,35 @@ class SearchBar(Gtk.Box):
|
|||
else:
|
||||
return True
|
||||
|
||||
def __on_key_press(self, widget, event):
|
||||
keyname = Gdk.keyval_name(event.keyval).lower()
|
||||
if keyname == 'escape' and self.search_button.get_active():
|
||||
if self.search_entry.is_focus():
|
||||
self.search_button.set_active(False)
|
||||
else:
|
||||
self.focus()
|
||||
|
||||
if not "is_locked" in dir(self.window) or not self.window.is_locked():
|
||||
if keyname == "backspace":
|
||||
if self.is_empty():
|
||||
self.search_button.set_active(False)
|
||||
return True
|
||||
|
||||
if event.state & Gdk.ModifierType.CONTROL_MASK:
|
||||
if keyname == 'f':
|
||||
self.search_button.set_active(not self.search_button.get_active())
|
||||
return True
|
||||
return False
|
||||
|
||||
def focus(self):
|
||||
self.search_entry.grab_focus_without_selecting()
|
||||
|
||||
def is_visible(self):
|
||||
return self.revealer.get_reveal_child()
|
||||
return self.get_reveal_child()
|
||||
|
||||
def is_empty(self):
|
||||
return len(self.search_entry.get_text()) == 0
|
||||
|
||||
def on_icon_pressed(self, entry, icon_pos, event):
|
||||
if icon_pos == Gtk.EntryIconPosition.SECONDARY:
|
||||
self.search_entry.set_text("")
|
||||
|
||||
def filter_applications(self, entry):
|
||||
data = entry.get_text().strip()
|
||||
if len(data) != 0:
|
||||
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
|
||||
"edit-clear-symbolic")
|
||||
entry.connect("icon-press", self.on_icon_pressed)
|
||||
else:
|
||||
entry.set_icon_from_icon_name(
|
||||
Gtk.EntryIconPosition.SECONDARY, None)
|
||||
self.list_accounts.set_filter_func(self.filter_func, data, False)
|
||||
self.listbox.set_filter_func(self.filter_func, data, False)
|
||||
|
|
|
@ -2,9 +2,7 @@ from gi import require_version
|
|||
require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
|
||||
from TwoFactorAuth.widgets.add_account import AddAccount
|
||||
from TwoFactorAuth.widgets.confirmation import ConfirmationMessage
|
||||
from TwoFactorAuth.widgets.account_row import AccountRow
|
||||
from TwoFactorAuth.widgets.search_bar import SearchBar
|
||||
from TwoFactorAuth.widgets.accounts_window import AccountsWindow
|
||||
from TwoFactorAuth.widgets.login_window import LoginWindow
|
||||
from TwoFactorAuth.widgets.no_account_window import NoAccountWindow
|
||||
from TwoFactorAuth.widgets.headerbar import HeaderBar
|
||||
|
@ -14,29 +12,16 @@ from gettext import gettext as _
|
|||
|
||||
|
||||
class Window(Gtk.ApplicationWindow):
|
||||
app = None
|
||||
selected_app_idx = None
|
||||
selected_count = 0
|
||||
counter = 0
|
||||
|
||||
hb = None
|
||||
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
login_box = None
|
||||
no_account_box = None
|
||||
apps_list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
apps_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
list_box = Gtk.ListBox()
|
||||
|
||||
|
||||
def __init__(self, application):
|
||||
self.app = application
|
||||
self.generate_window()
|
||||
self.generate_header_bar()
|
||||
self.generate_search_bar()
|
||||
self.generate_accounts_list()
|
||||
self.generate_accounts_box()
|
||||
self.generate_no_accounts_box()
|
||||
self.generate_login_form()
|
||||
self.generate_login_box()
|
||||
self.refresh_window()
|
||||
GLib.timeout_add_seconds(60, self.refresh_counter)
|
||||
|
||||
|
@ -60,56 +45,21 @@ class Window(Gtk.ApplicationWindow):
|
|||
"""
|
||||
Keyboard Listener handling
|
||||
"""
|
||||
keypress = Gdk.keyval_name(key_event.keyval).lower()
|
||||
if not self.app.locked:
|
||||
count = self.app.db.count()
|
||||
if count > 0:
|
||||
if self.list_box.get_selected_row():
|
||||
index = self.list_box.get_selected_row().get_index()
|
||||
else:
|
||||
index = 0
|
||||
selected_row = self.list_box.get_row_at_index(index)
|
||||
control_mask = Gdk.ModifierType.CONTROL_MASK
|
||||
if keypress == "c":
|
||||
if key_event.state == control_mask:
|
||||
selected_row.copy_code()
|
||||
elif keypress == "l":
|
||||
if key_event.state == control_mask:
|
||||
self.login_box.toggle_lock()
|
||||
elif keypress == "f":
|
||||
if key_event.state == control_mask:
|
||||
self.hb.toggle_search()
|
||||
elif keypress == "s":
|
||||
if key_event.state == control_mask:
|
||||
self.toggle_select()
|
||||
elif keypress == "n":
|
||||
if key_event.state == control_mask:
|
||||
keyname = Gdk.keyval_name(key_event.keyval).lower()
|
||||
if not self.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():
|
||||
self.toggle_select()
|
||||
return True
|
||||
|
||||
if keyname == "n":
|
||||
if key_event.state == Gdk.ModifierType.CONTROL_MASK:
|
||||
self.add_account()
|
||||
elif keypress == "delete" and not self.search_bar.is_visible():
|
||||
self.remove_account()
|
||||
elif keypress == "return":
|
||||
if count > 0:
|
||||
selected_row.toggle_code_box()
|
||||
elif keypress == "backspace":
|
||||
if self.search_bar.is_empty():
|
||||
self.hb.search_button.set_active(False)
|
||||
elif keypress == "escape":
|
||||
if self.search_bar.is_visible():
|
||||
self.hb.search_button.set_active(False)
|
||||
if not self.select_button.get_visible():
|
||||
self.toggle_select()
|
||||
elif keypress == "up" or keypress == "down":
|
||||
dx = -1 if keypress == "up" else 1
|
||||
if count != 0:
|
||||
row = self.list_box.get_selected_row()
|
||||
if row:
|
||||
index = row.get_index()
|
||||
index = (index + dx)%count
|
||||
selected_row = self.list_box.get_row_at_index(index)
|
||||
self.list_box.select_row(selected_row)
|
||||
else:
|
||||
if keypress == "return":
|
||||
self.login_box.on_unlock()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def refresh_counter(self):
|
||||
"""
|
||||
|
@ -123,44 +73,21 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.toggle_lock()
|
||||
return True
|
||||
|
||||
def generate_search_bar(self):
|
||||
"""
|
||||
Generate search bar box and entry
|
||||
"""
|
||||
self.search_bar = SearchBar(self.list_box)
|
||||
self.hb.search_button.connect("toggled", self.search_bar.toggle)
|
||||
|
||||
self.apps_box.pack_start(self.search_bar, False, True, 0)
|
||||
self.main_box.pack_start(self.apps_box, True, True, 0)
|
||||
|
||||
def remove_selected(self, *args):
|
||||
"""
|
||||
Remove selected accounts
|
||||
"""
|
||||
message = _("Do you really want to remove selected accounts?")
|
||||
confirmation = ConfirmationMessage(self, message)
|
||||
confirmation.show()
|
||||
if confirmation.get_confirmation():
|
||||
for row in self.list_box.get_children():
|
||||
checkbox = row.get_checkbox()
|
||||
if checkbox.get_active():
|
||||
label_id = row.get_id()
|
||||
row.kill()
|
||||
self.app.db.remove_by_id(label_id)
|
||||
self.list_box.remove(row)
|
||||
self.list_box.unselect_all()
|
||||
confirmation.destroy()
|
||||
self.toggle_select()
|
||||
self.refresh_window()
|
||||
|
||||
def generate_login_form(self):
|
||||
def generate_login_box(self):
|
||||
"""
|
||||
Generate login form
|
||||
"""
|
||||
self.login_box = LoginWindow(self.app)
|
||||
self.login_box = LoginWindow(self.app, self)
|
||||
self.hb.lock_button.connect("clicked", self.login_box.toggle_lock)
|
||||
self.main_box.pack_start(self.login_box, True, False, 0)
|
||||
|
||||
def generate_accounts_box(self):
|
||||
self.accounts_box = AccountsWindow(self.app, self)
|
||||
self.accounts_list = self.accounts_box.get_accounts_list()
|
||||
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)
|
||||
|
||||
def generate_header_bar(self):
|
||||
"""
|
||||
Generate a header bar box
|
||||
|
@ -169,7 +96,6 @@ class Window(Gtk.ApplicationWindow):
|
|||
# connect signals
|
||||
self.hb.cancel_button.connect("clicked", self.toggle_select)
|
||||
self.hb.select_button.connect("clicked", self.toggle_select)
|
||||
self.hb.remove_button.connect("clicked", self.remove_selected)
|
||||
self.hb.add_button.connect("clicked", self.add_account)
|
||||
self.set_titlebar(self.hb)
|
||||
|
||||
|
@ -185,96 +111,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
Toggle select mode
|
||||
"""
|
||||
self.hb.toggle_select_mode()
|
||||
pass_enabled = self.app.cfg.read("state", "login")
|
||||
is_select_mode = self.hb.is_on_select_mode()
|
||||
if is_select_mode:
|
||||
self.list_box.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
|
||||
else:
|
||||
self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
|
||||
if self.selected_app_idx:
|
||||
index = self.selected_app_idx
|
||||
else:
|
||||
index = 0
|
||||
list_row_box = self.list_box.get_row_at_index(index)
|
||||
self.list_box.select_row(list_row_box)
|
||||
|
||||
for row in self.list_box.get_children():
|
||||
checkbox = row.get_checkbox()
|
||||
code_label = row.get_code_label()
|
||||
visible = checkbox.get_visible()
|
||||
selected = checkbox.get_active()
|
||||
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")
|
||||
|
||||
checkbox.set_visible(not visible)
|
||||
checkbox.set_no_show_all(visible)
|
||||
|
||||
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.list_box.select_row(listbox_row)
|
||||
if is_visible:
|
||||
self.selected_count += 1
|
||||
else:
|
||||
self.list_box.unselect_row(listbox_row)
|
||||
if is_visible:
|
||||
self.selected_count -= 1
|
||||
self.hb.remove_button.set_sensitive(self.selected_count > 0)
|
||||
|
||||
def select_row(self, list_box, listbox_row):
|
||||
"""
|
||||
Select row @override the clicked event by default for ListBoxRow
|
||||
"""
|
||||
index = listbox_row.get_index()
|
||||
is_select_mode = self.hb.is_on_select_mode()
|
||||
checkbox = listbox_row.get_checkbox()
|
||||
if is_select_mode:
|
||||
checkbox.set_active(not checkbox.get_active())
|
||||
else:
|
||||
if self.selected_app_idx:
|
||||
selected_row = self.list_box.get_row_at_index(
|
||||
self.selected_app_idx)
|
||||
if selected_row:
|
||||
self.list_box.unselect_row(selected_row)
|
||||
self.selected_app_idx = index
|
||||
self.list_box.select_row(self.list_box.get_row_at_index(index))
|
||||
|
||||
def generate_accounts_list(self):
|
||||
"""
|
||||
Generate an account ListBox inside of a ScrolledWindow
|
||||
"""
|
||||
count = self.app.db.count()
|
||||
|
||||
# Create a ScrolledWindow for accounts
|
||||
self.list_box.get_style_context().add_class("applications-list")
|
||||
self.list_box.set_adjustment()
|
||||
self.list_box.connect("row_activated", self.select_row)
|
||||
self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
self.apps_list_box.pack_start(self.list_box, True, True, 0)
|
||||
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
scrolled_win.add_with_viewport(self.apps_list_box)
|
||||
self.apps_box.pack_start(scrolled_win, True, True, 0)
|
||||
|
||||
apps = self.app.db.fetch_apps()
|
||||
i = 0
|
||||
count = len(apps)
|
||||
while i < count:
|
||||
self.list_box.add(AccountRow(self, apps[i][0], apps[i][1], apps[i][2],
|
||||
apps[i][3]))
|
||||
i += 1
|
||||
self.accounts_list.toggle_select_mode()
|
||||
|
||||
def generate_no_accounts_box(self):
|
||||
"""
|
||||
|
@ -283,63 +120,28 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.no_account_box = NoAccountWindow()
|
||||
self.main_box.pack_start(self.no_account_box, True, False, 0)
|
||||
|
||||
def append_list_box(self, uid, name, secret_code, image):
|
||||
"""
|
||||
Add an element to the ListBox
|
||||
:param uid: account id
|
||||
:param name: account name
|
||||
:param secret_code: account secret code
|
||||
:param image: account image path or icon name
|
||||
"""
|
||||
secret_code = sha256(secret_code.encode('utf-8')).hexdigest()
|
||||
self.list_box.add(AccountRow(self, uid, name, secret_code, image))
|
||||
self.list_box.show_all()
|
||||
|
||||
def refresh_window(self):
|
||||
"""
|
||||
Refresh windows components
|
||||
"""
|
||||
is_locked = self.app.locked
|
||||
count = self.app.db.count()
|
||||
if is_locked:
|
||||
if self.is_locked():
|
||||
self.login_box.show()
|
||||
self.no_account_box.hide()
|
||||
self.apps_box.set_visible(False)
|
||||
self.apps_box.set_no_show_all(True)
|
||||
self.accounts_box.hide()
|
||||
else:
|
||||
self.login_box.hide()
|
||||
if count == 0:
|
||||
self.no_account_box.show()
|
||||
self.apps_box.set_visible(False)
|
||||
self.apps_box.set_no_show_all(True)
|
||||
self.accounts_box.hide()
|
||||
else:
|
||||
self.accounts_box.show()
|
||||
self.no_account_box.hide()
|
||||
self.apps_box.set_visible(True)
|
||||
self.apps_box.set_no_show_all(False)
|
||||
self.hb.refresh()
|
||||
self.main_box.show_all()
|
||||
self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
|
||||
def remove_account(self, *args):
|
||||
"""
|
||||
Remove an application
|
||||
"""
|
||||
if len(args) > 0:
|
||||
row = args[0].get_parent().get_parent().get_parent()
|
||||
self.list_box.select_row(row)
|
||||
|
||||
message = _("Do you really want to remove this account?")
|
||||
confirmation = ConfirmationMessage(self, message)
|
||||
confirmation.show()
|
||||
if confirmation.get_confirmation():
|
||||
if self.list_box.get_selected_row():
|
||||
selected_row = self.list_box.get_selected_row()
|
||||
app_id = selected_row.get_id()
|
||||
selected_row.kill()
|
||||
self.list_box.remove(selected_row)
|
||||
self.app.db.remove_by_id(app_id)
|
||||
confirmation.destroy()
|
||||
self.refresh_window()
|
||||
def is_locked(self):
|
||||
return self.app.locked
|
||||
|
||||
def save_window_state(self):
|
||||
"""
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-06-15 02:08+0200\n"
|
||||
"POT-Creation-Date: 2016-06-19 12:58+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -87,14 +87,14 @@ msgid "Selection mode"
|
|||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/headerbar.py:80
|
||||
#: TwoFactorAuth/widgets/applications_list.py:88
|
||||
#: TwoFactorAuth/widgets/applications_list.py:89
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/headerbar.py:84
|
||||
#: TwoFactorAuth/widgets/add_account.py:48
|
||||
#: TwoFactorAuth/widgets/change_password.py:143
|
||||
#: TwoFactorAuth/widgets/applications_list.py:80
|
||||
#: TwoFactorAuth/widgets/applications_list.py:81
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
|
@ -107,14 +107,6 @@ msgstr ""
|
|||
msgid "There's no account at the moment"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/window.py:140
|
||||
msgid "Do you really want to remove selected accounts?"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/window.py:340
|
||||
msgid "Do you really want to remove this account?"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/add_account.py:53
|
||||
msgid "Add"
|
||||
msgstr ""
|
||||
|
@ -147,44 +139,48 @@ msgstr ""
|
|||
msgid "Repeat new password"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/applications_list.py:75
|
||||
#: TwoFactorAuth/widgets/applications_list.py:76
|
||||
msgid "Select an application"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/applications_list.py:91
|
||||
#: TwoFactorAuth/widgets/applications_list.py:92
|
||||
msgid "Next"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/login_window.py:23
|
||||
#: TwoFactorAuth/widgets/login_window.py:25
|
||||
msgid "Enter your password"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/login_window.py:26
|
||||
#: TwoFactorAuth/widgets/login_window.py:28
|
||||
msgid "Unlock"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/account_row.py:161
|
||||
#: TwoFactorAuth/widgets/account_row.py:164
|
||||
msgid "Copy the generated code"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/account_row.py:171
|
||||
#: TwoFactorAuth/widgets/account_row.py:174
|
||||
msgid "Remove the account"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/account_row.py:177
|
||||
#: TwoFactorAuth/widgets/account_row.py:233
|
||||
#: TwoFactorAuth/widgets/account_row.py:179
|
||||
#: TwoFactorAuth/widgets/account_row.py:267
|
||||
#, python-format
|
||||
msgid "Expires in %s seconds"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/account_row.py:183
|
||||
#: TwoFactorAuth/widgets/account_row.py:185
|
||||
msgid "Error during the generation of code"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/account_row.py:229
|
||||
#: TwoFactorAuth/widgets/account_row.py:230
|
||||
msgid "Couldn't generate the secret code"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/account_row.py:256
|
||||
msgid "Do you really want to remove this account?"
|
||||
msgstr ""
|
||||
|
||||
#: TwoFactorAuth/widgets/settings.py:52
|
||||
msgid "Behavior"
|
||||
msgstr ""
|
||||
|
|
Loading…
Add table
Reference in a new issue