gtk2passwordapp 4.3.0 → 6.0.210202
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +107 -0
- data/bin/gtk2passwordapp +15 -119
- data/lib/gtk2passwordapp.rb +50 -14
- data/lib/gtk2passwordapp/account.rb +5 -5
- data/lib/gtk2passwordapp/accounts.rb +7 -6
- data/lib/gtk2passwordapp/cli.rb +35 -0
- data/lib/gtk2passwordapp/config.rb +197 -145
- data/lib/gtk2passwordapp/gui.rb +314 -0
- metadata +41 -80
- data/README.rdoc +0 -75
- data/data/VERSION +0 -1
- data/lib/gtk2passwordapp/gtk2passwordapp.rb +0 -453
- data/lib/gtk2passwordapp/such_parts.rb +0 -22
- data/lib/gtk2passwordapp/version.rb +0 -3
@@ -1,161 +1,213 @@
|
|
1
|
-
|
1
|
+
class Gtk2PasswordApp
|
2
2
|
using Rafini::String
|
3
|
+
extend Rafini::Empty
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
gtk2passwordapp [--no-gui [--dump [--verbose]]] [account]
|
8
|
-
HELP
|
9
|
-
|
10
|
-
APPDIR = File.dirname File.dirname __dir__
|
11
|
-
|
12
|
-
s0 = Rafini::Empty::STRING
|
13
|
-
h0 = Rafini::Empty::HASH
|
14
|
-
a0 = Rafini::Empty::ARRAY
|
5
|
+
H2Q = BaseConvert::FromTo.new(base: 16, digits: '0123456789ABCDEF', to_base:91, to_digits: :qgraph)
|
6
|
+
RND = SuperRandom.new
|
7
|
+
TOTPx = /^[A-Z2-7]{16,}$/
|
15
8
|
|
16
9
|
CONFIG = {
|
17
|
-
Help: help,
|
18
10
|
|
19
|
-
#
|
20
|
-
PwdFile: "#{XDG['CACHE']}/gtk3app/gtk2passwordapp/passwords.dat",
|
21
|
-
# Shared Secret File
|
22
|
-
# Consider using a file found in a removable flashdrive.
|
23
|
-
SharedSecretFile: "#{XDG['CACHE']}/gtk3app/gtk2passwordapp/key.ssss",
|
24
|
-
BackupFile: "#{ENV['HOME']}/Dropbox/gtk2passwordapp.bak",
|
11
|
+
# Hashing
|
25
12
|
|
26
|
-
|
27
|
-
|
13
|
+
Salt: s0,
|
14
|
+
LongPwd: 14,
|
28
15
|
|
29
|
-
#
|
30
|
-
TooOld: 60*60*24*365, # Year
|
16
|
+
# Miscellaneous Strings
|
31
17
|
|
32
|
-
#
|
33
|
-
QrcTimeOut: 9,
|
34
|
-
|
35
|
-
# Password Generators
|
36
|
-
Random: 'Random',
|
37
|
-
AlphaNumeric: 'Alpha-Numeric',
|
38
|
-
Custom: 'Caps',
|
39
|
-
CustomDigits: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
40
|
-
|
41
|
-
# Button Labels
|
42
|
-
Go: 'Go',
|
43
|
-
Edit: 'Edit',
|
44
|
-
Add: 'Add',
|
45
|
-
Goto: 'Goto',
|
46
|
-
Current: 'Current',
|
47
|
-
Previous: 'Previous',
|
48
|
-
Show: 'Show',
|
49
|
-
Cancel: 'Cancel',
|
50
|
-
Delete: 'Delete',
|
51
|
-
Save: 'Save',
|
52
|
-
|
53
|
-
# Labels
|
54
|
-
ReTry: 'Try Again!',
|
18
|
+
Logo: "#{UserSpace::XDG['data']}/gtk3app/gtk2passwordapp/logo.png",
|
55
19
|
HiddenPwd: ' * * * ',
|
56
20
|
|
21
|
+
# Overriding Gtk3App's window, main:
|
22
|
+
|
23
|
+
main: { set_title: 'Password Manager' },
|
24
|
+
|
25
|
+
# Overriding Gtk3App's toolbar
|
26
|
+
toolbar: {
|
27
|
+
set_expanded: true,
|
28
|
+
into: [:pack_start, expand:true, fill:true, padding:4],
|
29
|
+
},
|
30
|
+
|
31
|
+
# Overriding Gtk3App's app_menu:
|
32
|
+
|
33
|
+
app_menu: {
|
34
|
+
add_menu_item: [:minime!,:about!,:quit!],
|
35
|
+
},
|
36
|
+
|
57
37
|
# Colors
|
58
|
-
Blue: '#00F',
|
59
|
-
Red: '#F00',
|
60
|
-
Black: '#000',
|
61
38
|
|
62
|
-
#
|
63
|
-
|
39
|
+
Red: '#900',
|
40
|
+
Green: '#090',
|
41
|
+
Blue: '#009',
|
42
|
+
TooOld: 60*60*24*365, # Year
|
43
|
+
|
44
|
+
# Buttons
|
45
|
+
|
46
|
+
tool_button: {
|
47
|
+
set_width_request: 1,
|
48
|
+
into: [:pack_start, expand:true, fill:true, padding:1],
|
49
|
+
},
|
50
|
+
|
51
|
+
# Spin Buttons
|
52
|
+
|
53
|
+
PWDLEN: [3,40,1],
|
54
|
+
pwdlen: {set_value: 13},
|
55
|
+
pwdlen!: [:PWDLEN,:pwdlen],
|
56
|
+
|
57
|
+
# Tools Labels
|
58
|
+
|
59
|
+
ADD: [label: 'Add'],
|
60
|
+
EDIT: [label: 'Edit'],
|
61
|
+
GO: [label: 'Go'],
|
62
|
+
CANCEL: [label: 'Cancel'],
|
63
|
+
DELETE: [label: 'Delete'],
|
64
|
+
SAVE: [label: 'Save'],
|
65
|
+
CURRENT: [label: 'Current'],
|
66
|
+
PREVIOUS: [label: 'Previous'],
|
67
|
+
RAND: [label: 'Random'],
|
68
|
+
|
69
|
+
# Initialize
|
70
|
+
# Stage and Toolbar
|
71
|
+
|
64
72
|
ClipboardTimeout: 15,
|
73
|
+
PwdFile: "#{UserSpace::XDG['cache']}/gtk3app/gtk2passwordapp/dump.yzb",
|
74
|
+
|
75
|
+
# Logo's Main Menu
|
76
|
+
|
77
|
+
MAIN_MENU: a0,
|
78
|
+
main_menu: h0,
|
79
|
+
main_menu!: [:MAIN_MENU,:main_menu],
|
80
|
+
|
81
|
+
main_menu_item: h0,
|
82
|
+
|
83
|
+
# Toolbar's Toolbox
|
84
|
+
|
85
|
+
TOOLBOX: [:horizontal],
|
86
|
+
toolbox: h0,
|
87
|
+
toolbox!: [:TOOLBOX,:toolbox],
|
88
|
+
|
89
|
+
# Stage's Pages
|
90
|
+
|
91
|
+
PAGES: [:vertical],
|
92
|
+
pages: h0,
|
93
|
+
pages!: [:PAGES,:pages],
|
94
|
+
|
95
|
+
# Page
|
96
|
+
|
97
|
+
PAGE: [:vertical],
|
98
|
+
page: h0,
|
99
|
+
page!: [:PAGE,:page],
|
100
|
+
|
101
|
+
# Page Label
|
102
|
+
|
103
|
+
page_label: h0,
|
104
|
+
|
105
|
+
# Field Row
|
106
|
+
|
107
|
+
FIELD_ROW: [:horizontal],
|
108
|
+
field_row: h0,
|
109
|
+
field_row!: [:FIELD_ROW,:field_row],
|
110
|
+
|
111
|
+
# Field Label
|
112
|
+
|
113
|
+
FIELD_LABEL: a0,
|
114
|
+
field_label: {
|
115
|
+
set_selectable: false,
|
116
|
+
set_width_request: 80,
|
117
|
+
set_alignment: [1.0,0.5],
|
118
|
+
set_padding: [4,4],
|
119
|
+
},
|
120
|
+
field_label!: [:FIELD_LABEL, :field_label],
|
121
|
+
|
122
|
+
# Field View
|
123
|
+
|
124
|
+
FIELD_VIEW: a0,
|
125
|
+
field_view: {
|
126
|
+
set_selectable: true,
|
127
|
+
set_width_request: 250,
|
128
|
+
set_alignment: [0.0,0.5],
|
129
|
+
set_padding: [4,4],
|
130
|
+
},
|
131
|
+
field_view!: [:FIELD_VIEW, :field_view],
|
132
|
+
|
133
|
+
# Field Entry
|
134
|
+
|
135
|
+
FIELD_ENTRY: a0,
|
136
|
+
field_entry: {
|
137
|
+
set_width_request: 250,
|
138
|
+
into: [:pack_start, expand:true, fill:true, padding:4],
|
139
|
+
},
|
140
|
+
field_entry!: [:FIELD_ENTRY,:field_entry],
|
141
|
+
|
142
|
+
# Password Entry
|
143
|
+
|
144
|
+
PASSWORD_ENTRY: a0,
|
145
|
+
password_entry: {
|
146
|
+
set_visibility: false,
|
147
|
+
set_width_request: 250,
|
148
|
+
into: [:pack_start, expand:true, fill:true, padding:4],
|
149
|
+
},
|
150
|
+
password_entry!: [:PASSWORD_ENTRY,:password_entry],
|
151
|
+
|
152
|
+
# Error Label
|
153
|
+
|
154
|
+
ERROR_LABEL: a0,
|
155
|
+
error_label: h0,
|
156
|
+
error_label!: [:ERROR_LABEL,:error_label],
|
157
|
+
|
158
|
+
# Password Page
|
159
|
+
|
160
|
+
MinPwdLen: 7,
|
161
|
+
Confirm: "Confirm password!",
|
162
|
+
|
163
|
+
# Page Labels
|
164
|
+
|
165
|
+
PASSWORD_PAGE_LABEL: ['Enter Master Password'],
|
166
|
+
ADD_PAGE_LABEL: ['Add Account'],
|
167
|
+
EDIT_PAGE_LABEL: ['Edit Account'],
|
168
|
+
MAIN_PAGE_LABEL: ['View Account'],
|
169
|
+
|
170
|
+
# Fields
|
171
|
+
|
172
|
+
NAME: ['Name:'],
|
173
|
+
URL: ['URL:'],
|
174
|
+
NOTE: ['Note:'],
|
175
|
+
USERNAME: ['Username:'],
|
176
|
+
PASSWORD: ['Password:'],
|
177
|
+
|
178
|
+
# About Dialog
|
179
|
+
|
180
|
+
about_dialog: {
|
181
|
+
set_program_name: 'Password Manager',
|
182
|
+
set_version: VERSION.semantic(0..1),
|
183
|
+
set_copyright: '(c) 2021 CarlosJHR64',
|
184
|
+
set_comments: 'A Gtk3App Password Manager',
|
185
|
+
set_website: 'https://github.com/carlosjhr64/gtk2passwordapp',
|
186
|
+
set_website_label: 'See it at GitHub!',
|
187
|
+
},
|
188
|
+
|
189
|
+
# Delete Dialog
|
190
|
+
|
191
|
+
DELETE_URSURE: a0,
|
192
|
+
delete_ursure: {add_label: 'Delete?'},
|
193
|
+
delete_ursure!: [:DELETE_URSURE,:delete_ursure],
|
194
|
+
|
195
|
+
# Reset Dialog
|
196
|
+
|
197
|
+
RESET_URSURE: a0,
|
198
|
+
reset_ursure: {add_label: 'Reset Master Password?'},
|
199
|
+
reset_ursure!: [:RESET_URSURE,:reset_ursure],
|
200
|
+
|
201
|
+
# Errors
|
202
|
+
|
203
|
+
BadUrl: 'URL must be like http://site.',
|
204
|
+
BadUsername: 'Username must be all graph.',
|
205
|
+
BadPassword: 'Password must be all graph.',
|
206
|
+
BadName: 'Account name must be a non-empty String.',
|
207
|
+
CipherError: 'Decryption error.',
|
208
|
+
AccountHit: 'Account exists.',
|
209
|
+
AccountMiss: 'Account does NOT exist.',
|
210
|
+
TooShort: "Password too short!",
|
65
211
|
|
66
|
-
# Fields' Labels
|
67
|
-
Name: 'Account:',
|
68
|
-
FIELDS: [
|
69
|
-
[:url, 'Url:' ],
|
70
|
-
[:note, 'Note:' ],
|
71
|
-
[:username, 'Username:'],
|
72
|
-
[:password, 'Password:'],
|
73
|
-
],
|
74
|
-
FIELD_ALIGNMENT: [0.0, 0.5],
|
75
|
-
|
76
|
-
# Such::Thing::PARAMETERS
|
77
|
-
thing: {
|
78
|
-
|
79
|
-
box: h0,
|
80
|
-
label: h0,
|
81
|
-
check_button: h0,
|
82
|
-
entry: h0,
|
83
|
-
|
84
|
-
button: {
|
85
|
-
set_width_request: 75,
|
86
|
-
into: [:pack_start, expand:true, fill:true, padding:0],
|
87
|
-
},
|
88
|
-
|
89
|
-
vbox!: [[:vertical], :box, s0],
|
90
|
-
hbox!: [[:horizontal], :box, s0],
|
91
|
-
|
92
|
-
prompt: {
|
93
|
-
set_width_request: 75,
|
94
|
-
set_alignment: [1.0, 0.5],
|
95
|
-
set_padding: [4,4],
|
96
|
-
},
|
97
|
-
prompt!: [a0, :prompt],
|
98
|
-
|
99
|
-
prompted: {
|
100
|
-
set_width_request: 325,
|
101
|
-
},
|
102
|
-
prompted!: [a0, :prompted],
|
103
|
-
|
104
|
-
a!: [a0, :button],
|
105
|
-
b!: [a0, :button],
|
106
|
-
c!: [a0, :button],
|
107
|
-
|
108
|
-
window: {
|
109
|
-
set_title: 'Password Manager',
|
110
|
-
set_window_position: :center,
|
111
|
-
},
|
112
|
-
|
113
|
-
password_label!: [['Password:'], :label],
|
114
|
-
password_entry!: [a0, :entry, {set_visibility: false}],
|
115
|
-
|
116
|
-
edit_label!: [['Edit Account'], :label],
|
117
|
-
add_label!: [['Add Account'], :label],
|
118
|
-
view_label!: [['View Account'], :label],
|
119
|
-
|
120
|
-
pwd_size_check!: [:check_button],
|
121
|
-
pwd_size_spin!: [
|
122
|
-
[4,64,1],
|
123
|
-
{
|
124
|
-
set_increments: [1,10],
|
125
|
-
set_digits: 0,
|
126
|
-
set_value: 14,
|
127
|
-
},
|
128
|
-
],
|
129
|
-
|
130
|
-
reset!: [['Reset Master Password'], 'activate'],
|
131
|
-
backup!: [['Backup Passwords'], 'activate'],
|
132
|
-
|
133
|
-
about_dialog: {
|
134
|
-
set_program_name: 'Password Manager',
|
135
|
-
set_version: VERSION.semantic(0..1),
|
136
|
-
set_copyright: '(c) 2014 CarlosJHR64',
|
137
|
-
set_comments: 'A Gtk3App Password Manager',
|
138
|
-
set_website: 'https://github.com/carlosjhr64/gtk2passwordapp',
|
139
|
-
set_website_label: 'See it at GitHub!',
|
140
|
-
},
|
141
|
-
HelpFile: 'https://github.com/carlosjhr64/gtk2passwordapp',
|
142
|
-
Logo: "#{XDG['DATA']}/gtk3app/gtk2passwordapp/logo.png",
|
143
|
-
|
144
|
-
backup_dialog: {
|
145
|
-
set_title: 'Backup Passwords',
|
146
|
-
set_window_position: :center_on_parent,
|
147
|
-
},
|
148
|
-
|
149
|
-
error_dialog: {
|
150
|
-
set_text: 'Backup Error',
|
151
|
-
set_window_position: :center_on_parent,
|
152
|
-
},
|
153
|
-
|
154
|
-
delete_dialog: {
|
155
|
-
set_window_position: :center_on_parent,
|
156
|
-
},
|
157
|
-
delete_label!: [['Delete?'], :label],
|
158
|
-
|
159
|
-
}
|
160
212
|
}
|
161
213
|
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
class Gtk2PasswordApp
|
2
|
+
def initialize(stage, toolbar, options)
|
3
|
+
@primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
|
4
|
+
@clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
|
5
|
+
@accounts = Accounts.new(CONFIG[:PwdFile])
|
6
|
+
@toolbox = Such::Box.new toolbar, :toolbox!
|
7
|
+
@pages = Such::Box.new stage, :pages!
|
8
|
+
@recent = []
|
9
|
+
@reset = false
|
10
|
+
@red,@green,@blue = [:Red,:Green,:Blue].map{Gdk::RGBA.parse(CONFIG[_1])}
|
11
|
+
@tools = @password_page = @add_page = @edit_page = @main_page = @menu = nil
|
12
|
+
build_password_page
|
13
|
+
build_logo_menu
|
14
|
+
end
|
15
|
+
|
16
|
+
def build_logo_menu
|
17
|
+
Gtk3App.logo_press_event do |button|
|
18
|
+
next unless @main_page&.visible?
|
19
|
+
case button
|
20
|
+
when 1
|
21
|
+
popup_accounts_menu unless @accounts.data.empty?
|
22
|
+
when 2
|
23
|
+
reset_password
|
24
|
+
when 3
|
25
|
+
# Gets captured by Gtk3App's main menu
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_password
|
31
|
+
ursure = Gtk3App::YesNoDialog.new :reset_ursure!
|
32
|
+
Gtk3App.transient ursure
|
33
|
+
if ursure.ok?
|
34
|
+
@reset = true
|
35
|
+
hide_main_page
|
36
|
+
@password_page.show
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def popup_accounts_menu
|
41
|
+
@menu = Such::Menu.new :main_menu!
|
42
|
+
@recent.each do |name| add_menu_item(name, @green) end
|
43
|
+
@accounts.data.keys.sort{|a,b|a.upcase<=>b.upcase}.each do |name| add_menu_item(name) end
|
44
|
+
@menu.show_all
|
45
|
+
@menu.popup_at_pointer
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_menu_item(name, color=nil)
|
49
|
+
menu_item = Such::MenuItem.new [label:name], :main_menu_item, 'activate' do selected_account(name) end
|
50
|
+
account = @accounts.get name
|
51
|
+
color = (Time.now.to_i - account.updated > CONFIG[:TooOld])? @red : @blue unless color
|
52
|
+
menu_item.override_color :normal, color
|
53
|
+
@menu.append menu_item
|
54
|
+
end
|
55
|
+
|
56
|
+
def selected_account(name)
|
57
|
+
@recent.unshift name; @recent.uniq!; @recent.pop if @recent.length>3
|
58
|
+
account = @accounts.get name
|
59
|
+
setup_main_page account
|
60
|
+
setup_edit_page account
|
61
|
+
copy2clipboard(account.password, account.username)
|
62
|
+
end
|
63
|
+
|
64
|
+
def copy2clipboard(pwd, user)
|
65
|
+
@primary.text, @clipboard.text = pwd, user
|
66
|
+
GLib::Timeout.add_seconds(CONFIG[:ClipboardTimeout]) do
|
67
|
+
@primary.text = '' if @primary.wait_for_text == pwd
|
68
|
+
@clipboard.text = '' if @clipboard.wait_for_text == user
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def view_row(page, label)
|
74
|
+
row = Such::Box.new page, :field_row!
|
75
|
+
Such::Label.new(row, label, :field_label)
|
76
|
+
Such::Label.new(row, :field_view!)
|
77
|
+
end
|
78
|
+
|
79
|
+
def field_row(page, label, entry=:field_entry!, &block)
|
80
|
+
row = Such::Box.new page, :field_row!
|
81
|
+
Such::Label.new(row, label, :field_label)
|
82
|
+
Such::Entry.new(row, entry, ((block)? 'activate' : ''), &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def show_main_page
|
86
|
+
@main_page.show_all
|
87
|
+
@toolbox.show_all
|
88
|
+
end
|
89
|
+
|
90
|
+
def hide_main_page
|
91
|
+
@main_page.hide
|
92
|
+
@toolbox.hide
|
93
|
+
end
|
94
|
+
|
95
|
+
def bootstrap_setups
|
96
|
+
names = @accounts.data.keys
|
97
|
+
account = (names.empty?)? nil : @accounts.get(names[rand(names.length)])
|
98
|
+
setup_edit_page(account) if account
|
99
|
+
setup_main_page(account)
|
100
|
+
end
|
101
|
+
|
102
|
+
def rehash(pwd)
|
103
|
+
pwd += CONFIG[:Salt] if pwd.length < CONFIG[:LongPwd]
|
104
|
+
pwd = Digest::SHA256.hexdigest(pwd).upcase
|
105
|
+
H2Q.convert pwd
|
106
|
+
end
|
107
|
+
|
108
|
+
def build_password_page
|
109
|
+
@password_page = Such::Box.new @pages, :page!
|
110
|
+
Such::Label.new @password_page, :PASSWORD_PAGE_LABEL, :page_label
|
111
|
+
error_label,previous = nil,'' # updates below
|
112
|
+
password_entry = field_row(@password_page, :PASSWORD, :password_entry!) do
|
113
|
+
pwd = password_entry.text.strip
|
114
|
+
password_entry.text = ''
|
115
|
+
raise CONFIG[:TooShort] if pwd.length < CONFIG[:MinPwdLen]
|
116
|
+
if not @reset and @accounts.exist?
|
117
|
+
@accounts.load rehash pwd
|
118
|
+
else
|
119
|
+
raise CONFIG[:Confirm] unless pwd==previous
|
120
|
+
@accounts.save rehash pwd
|
121
|
+
@reset = false
|
122
|
+
end
|
123
|
+
@password_page.hide
|
124
|
+
build_add_page unless @add_page
|
125
|
+
build_edit_page unless @edit_page
|
126
|
+
build_main_page unless @main_page
|
127
|
+
build_tools unless @tools
|
128
|
+
bootstrap_setups
|
129
|
+
show_main_page
|
130
|
+
rescue
|
131
|
+
error_label.text = $!.message
|
132
|
+
previous = pwd
|
133
|
+
end
|
134
|
+
error_label = Such::Label.new @password_page, :error_label!
|
135
|
+
end
|
136
|
+
|
137
|
+
def build_add_page
|
138
|
+
@add_page = Such::Box.new @pages, :page!
|
139
|
+
Such::Label.new @add_page, :ADD_PAGE_LABEL, :page_label
|
140
|
+
error_label = nil # updates below
|
141
|
+
add_account_entry = field_row(@add_page, :NAME) do
|
142
|
+
name = add_account_entry.text.strip
|
143
|
+
@accounts.add name
|
144
|
+
@accounts.save
|
145
|
+
account = @accounts.get name
|
146
|
+
setup_edit_page(account)
|
147
|
+
setup_main_page(account)
|
148
|
+
@add_page.hide
|
149
|
+
add_account_entry.text = ''
|
150
|
+
@edit_page.show_all
|
151
|
+
rescue
|
152
|
+
error_label.text = $!.message
|
153
|
+
end
|
154
|
+
Such::Button.new @add_page, :CANCEL, :tool_button do
|
155
|
+
@add_page.hide
|
156
|
+
add_account_entry.text = ''
|
157
|
+
show_main_page
|
158
|
+
end
|
159
|
+
error_label = Such::Label.new @add_page, :error_label!
|
160
|
+
end
|
161
|
+
|
162
|
+
def visibility_toggleling(entry)
|
163
|
+
entry.signal_connect('enter-notify-event') do
|
164
|
+
entry.set_visibility true unless entry.has_focus?
|
165
|
+
end
|
166
|
+
entry.signal_connect('leave-notify-event') do
|
167
|
+
entry.set_visibility false unless entry.has_focus?
|
168
|
+
end
|
169
|
+
entry.signal_connect('focus-in-event') do
|
170
|
+
entry.set_visibility true
|
171
|
+
end
|
172
|
+
entry.signal_connect('focus-out-event') do
|
173
|
+
entry.set_visibility false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def show_toggleling(label)
|
178
|
+
label.signal_connect('button-press-event') do |_,e|
|
179
|
+
if e.button==1 and not (pwd=label.text).empty?
|
180
|
+
case pwd
|
181
|
+
when CONFIG[:HiddenPwd]
|
182
|
+
label.text = @accounts.get(@name.text).password
|
183
|
+
when TOTPx
|
184
|
+
label.text = TOTP.passwords(label.text)[1].to_s
|
185
|
+
else
|
186
|
+
label.text = CONFIG[:HiddenPwd]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def build_edit_page
|
193
|
+
@edit_page = Such::Box.new @pages, :page!
|
194
|
+
Such::Label.new @edit_page, :EDIT_PAGE_LABEL, :page_label
|
195
|
+
|
196
|
+
@edit_name = view_row @edit_page, :NAME
|
197
|
+
@edit_url = field_row @edit_page, :URL
|
198
|
+
@edit_note = field_row @edit_page, :NOTE
|
199
|
+
@edit_username = field_row @edit_page, :USERNAME
|
200
|
+
@edit_password = field_row @edit_page, :PASSWORD, :password_entry!
|
201
|
+
|
202
|
+
visibility_toggleling @edit_password
|
203
|
+
|
204
|
+
generator = Such::Box.new @edit_page, :toolbox!
|
205
|
+
pwdlen=rndpwd=nil
|
206
|
+
Such::Button.new generator, :RAND, :tool_button do
|
207
|
+
rndpwd = H2Q.convert RND.hexadecimal
|
208
|
+
@edit_password.text = rndpwd[0...pwdlen.value]
|
209
|
+
end
|
210
|
+
pwdlen = Such::SpinButton.new generator, :pwdlen!, 'value-changed' do
|
211
|
+
@edit_password.text = rndpwd[0...pwdlen.value] if rndpwd
|
212
|
+
end
|
213
|
+
|
214
|
+
toolbox = Such::Box.new @edit_page, :toolbox!
|
215
|
+
error_label = Such::Label.new @edit_page, :error_label!
|
216
|
+
|
217
|
+
Such::Button.new toolbox, :SAVE, :tool_button do
|
218
|
+
account = @accounts.get @edit_name.text.strip
|
219
|
+
account.url = @edit_url.text.strip
|
220
|
+
account.note = @edit_note.text.strip
|
221
|
+
account.username = @edit_username.text.strip
|
222
|
+
account.password = @edit_password.text.strip
|
223
|
+
@accounts.save
|
224
|
+
rndpwd = nil
|
225
|
+
@edit_page.hide
|
226
|
+
setup_main_page(account)
|
227
|
+
show_main_page
|
228
|
+
rescue
|
229
|
+
error_label.text = $!.message
|
230
|
+
end
|
231
|
+
Such::Button.new toolbox, :CANCEL, :tool_button do
|
232
|
+
account = @accounts.get @edit_name.text
|
233
|
+
rndpwd = nil
|
234
|
+
@edit_page.hide
|
235
|
+
setup_edit_page(account) # restore values
|
236
|
+
show_main_page
|
237
|
+
end
|
238
|
+
Such::Button.new toolbox, :DELETE, :tool_button do
|
239
|
+
ursure = Gtk3App::YesNoDialog.new :delete_ursure!
|
240
|
+
Gtk3App.transient ursure
|
241
|
+
if ursure.ok?
|
242
|
+
@accounts.delete @edit_name.text
|
243
|
+
@accounts.save
|
244
|
+
rndpwd = nil
|
245
|
+
@edit_page.hide
|
246
|
+
bootstrap_setups
|
247
|
+
show_main_page
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def setup_edit_page(account)
|
253
|
+
@edit_name.text = account.name
|
254
|
+
@edit_url.text = account.url
|
255
|
+
@edit_note.text = account.note
|
256
|
+
@edit_username.text = account.username
|
257
|
+
@edit_password.text = account.password
|
258
|
+
end
|
259
|
+
|
260
|
+
def build_main_page
|
261
|
+
@main_page = Such::Box.new @pages, :page!
|
262
|
+
Such::Label.new @main_page, :MAIN_PAGE_LABEL, :page_label
|
263
|
+
|
264
|
+
@name = view_row @main_page, :NAME
|
265
|
+
@url = view_row @main_page, :URL
|
266
|
+
@note = view_row @main_page, :NOTE
|
267
|
+
@username = view_row @main_page, :USERNAME
|
268
|
+
@password = view_row @main_page, :PASSWORD
|
269
|
+
|
270
|
+
show_toggleling @password
|
271
|
+
end
|
272
|
+
|
273
|
+
def setup_main_page(account)
|
274
|
+
@name.text = account&.name || ''
|
275
|
+
@url.text = account&.url || ''
|
276
|
+
@note.text = account&.note || ''
|
277
|
+
@username.text = account&.username || ''
|
278
|
+
@password.text =(account&.password&.>'')? CONFIG[:HiddenPwd] : ''
|
279
|
+
end
|
280
|
+
|
281
|
+
def build_tools
|
282
|
+
@tools = true
|
283
|
+
Such::Button.new @toolbox, :ADD, :tool_button do
|
284
|
+
hide_main_page
|
285
|
+
@add_page.show_all
|
286
|
+
end
|
287
|
+
Such::Button.new @toolbox, :EDIT, :tool_button do
|
288
|
+
unless (name=@name.text).empty?
|
289
|
+
hide_main_page
|
290
|
+
@edit_page.show_all
|
291
|
+
end
|
292
|
+
end
|
293
|
+
Such::Button.new @toolbox, :GO, :tool_button do
|
294
|
+
unless (name=@name.text).empty?
|
295
|
+
url = @accounts.get(name).url
|
296
|
+
system(Gtk3App::CONFIG[:Open], url) unless url.empty?
|
297
|
+
end
|
298
|
+
end
|
299
|
+
Such::Button.new @toolbox, :CURRENT, :tool_button do
|
300
|
+
unless (name=@name.text).empty?
|
301
|
+
account = @accounts.get name
|
302
|
+
copy2clipboard(account.password, account.username)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
Such::Button.new @toolbox, :PREVIOUS, :tool_button do
|
306
|
+
unless (name=@name.text).empty?
|
307
|
+
account = @accounts.get name
|
308
|
+
copy2clipboard(account.previous, account.password)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def self.run = Gtk3App.run(klass:Gtk2PasswordApp)
|
314
|
+
end
|