mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
huge code modification! do not test it for now:)
This commit is contained in:
parent
e1380cc11b
commit
56c5624cc4
28 changed files with 1508 additions and 1278 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -105,3 +105,4 @@ po/Makefile.in.in
|
|||
*.compiled
|
||||
*.desktop
|
||||
*.gresource
|
||||
*.ui~
|
||||
|
|
|
@ -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
3
Authenticator/const.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from Authenticator.models.settings import Settings
|
||||
|
||||
settings = Settings.new()
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
289
Authenticator/widgets/accounts.py
Normal file
289
Authenticator/widgets/accounts.py
Normal 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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
53
data/application_row.ui
Normal 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
149
data/applications.ui
Normal 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
157
data/change_password.ui
Normal 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
41
data/login.ui
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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
163
data/settings.ui
Normal 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>
|
Loading…
Add table
Reference in a new issue