ruby-pass-qt 1.0.0

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.
@@ -0,0 +1,111 @@
1
+ module PassQt
2
+ class BrowseStoresDialog < RubyQt6::Bando::QDialog
3
+ DataItem = Struct.new(:tablewidgetitem)
4
+
5
+ q_object do
6
+ signal "stores_changed()"
7
+ slot "_on_list_add_button_clicked()"
8
+ slot "_on_list_remove_button_clicked()"
9
+ end
10
+
11
+ def initialize
12
+ super
13
+
14
+ @dataitems = {}
15
+
16
+ initialize_toolbar
17
+ initialize_tablewidget
18
+
19
+ mainlayout = QVBoxLayout.new(self)
20
+ mainlayout.add_stretch
21
+ mainlayout.add_widget(@toolbar)
22
+ mainlayout.add_widget(@tablewidget)
23
+ mainlayout.add_stretch
24
+
25
+ set_attribute(Qt::WA_DeleteOnClose)
26
+ set_modal(true)
27
+ end
28
+
29
+ private
30
+
31
+ def initialize_toolbar
32
+ @toolbar = QWidget.new
33
+
34
+ btn = QPushButton.new(QIcon.from_theme(QIcon::ThemeIcon::ListAdd), "Add")
35
+ btn.clicked.connect(self, :_on_list_add_button_clicked)
36
+
37
+ qhboxlayout = QHBoxLayout.new(@toolbar)
38
+ qhboxlayout.add_widget(QLabel.new("Password Stores"))
39
+ qhboxlayout.add_stretch
40
+ qhboxlayout.add_widget(btn)
41
+ end
42
+
43
+ def initialize_tablewidget
44
+ @tablewidget = QWidget.new
45
+ @tablewidget.set_object_name("browsestoresdialog_tablewidget")
46
+ @tablewidget.set_style_sheet("
47
+ #browsestoresdialog_tablewidget {
48
+ min-width: 600px;
49
+ background: white;
50
+ }
51
+ ")
52
+
53
+ QVBoxLayout.new(@tablewidget)
54
+ PassQt.settings.GET_stores.each do |store|
55
+ update_tablewidget_addtableitem(store["fullpath"])
56
+ end
57
+ end
58
+
59
+ def update_tablewidget_addtableitem(fullpath)
60
+ tableitem = QWidget.new
61
+ tableitemlayout = QHBoxLayout.new(tableitem)
62
+
63
+ label = QLabel.new(fullpath)
64
+ tableitemlayout.add_widget(label)
65
+ tableitemlayout.add_stretch
66
+
67
+ btn = QPushButton.new(QIcon.from_theme(QIcon::ThemeIcon::ListRemove), "Remove")
68
+ btn.clicked.connect(self, :_on_list_remove_button_clicked)
69
+ tableitemlayout.add_widget(btn)
70
+
71
+ @dataitems[fullpath] = DataItem.new(tableitem)
72
+ @tablewidget.layout.add_widget(tableitem)
73
+ end
74
+
75
+ def update_tablewidget_removetableitem(tableitem)
76
+ @dataitems.delete_if { |_, item| item.tablewidgetitem == tableitem }
77
+ tableitem.delete_now
78
+ end
79
+
80
+ def h_put_stores
81
+ stores = @dataitems.map { |fullpath, _| {"fullpath" => fullpath} }
82
+ PassQt.settings.PUT_stores(stores)
83
+ stores_changed.emit
84
+ end
85
+
86
+ def _on_list_add_button_clicked
87
+ dir = QFileDialog.get_existing_directory(self, "", QDir.home_path)
88
+ return if dir.empty?
89
+
90
+ if @dataitems.key?(dir)
91
+ message = "<p>The following store already exists.</p>#{dir}"
92
+ QMessageBox.critical(self, "", message)
93
+ return
94
+ end
95
+
96
+ unless QDir.new(dir).exists(".gpg-id")
97
+ message = "<p>The following store is invalid, because the .gpg-id file is missing.</p>#{dir}"
98
+ QMessageBox.critical(self, "", message)
99
+ return
100
+ end
101
+
102
+ update_tablewidget_addtableitem(dir)
103
+ h_put_stores
104
+ end
105
+
106
+ def _on_list_remove_button_clicked
107
+ update_tablewidget_removetableitem(sender.parent)
108
+ h_put_stores
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,80 @@
1
+ module PassQt
2
+ class MainWindow < RubyQt6::Bando::QMainWindow
3
+ q_object do
4
+ slot "_on_browse_action_triggered()"
5
+ slot "_on_browsestoresdialog_stores_changed()"
6
+ slot "_on_passlistwidget_store_changed(QString)"
7
+ slot "_on_passlistwidget_passfile_selected(QString,QString)"
8
+ slot "_on_passlistwidget_passfolder_selected(QString,QString)"
9
+ end
10
+
11
+ def initialize
12
+ super
13
+
14
+ initialize_toolbar
15
+ initialize_central_widget
16
+
17
+ set_context_menu_policy(Qt::NoContextMenu)
18
+ PassQt.settings.GET_mainwindow_geometry_and_restore_to(self)
19
+ end
20
+
21
+ def close_event(evt)
22
+ PassQt.settings.PUT_mainwindow_geometry(self)
23
+
24
+ _close_event(evt)
25
+ end
26
+
27
+ private
28
+
29
+ def initialize_toolbar
30
+ browse_action = QAction.new(QIcon.from_theme(QIcon::ThemeIcon::Computer), "Browse")
31
+ browse_action.set_tool_tip("Browse password stores")
32
+ browse_action.triggered.connect(self, :_on_browse_action_triggered)
33
+
34
+ toolbar = add_tool_bar("ToolBar")
35
+ toolbar.set_object_name("mainwindow_toolbar")
36
+ toolbar.set_movable(false)
37
+ toolbar.add_action(browse_action)
38
+ end
39
+
40
+ def initialize_central_widget
41
+ @passlistwidget = PassListWidget.new
42
+ @passlistwidget.store_changed.connect(self, :_on_passlistwidget_store_changed)
43
+ @passlistwidget.passfile_selected.connect(self, :_on_passlistwidget_passfile_selected)
44
+ @passlistwidget.passfolder_selected.connect(self, :_on_passlistwidget_passfolder_selected)
45
+
46
+ @passinfowidget = PassInfoWidget.new
47
+
48
+ centralwidget = QWidget.new
49
+ mainlayout = QHBoxLayout.new(centralwidget)
50
+ mainlayout.add_widget(@passlistwidget)
51
+ mainlayout.add_widget(@passinfowidget)
52
+ mainlayout.set_stretch(0, 2)
53
+ mainlayout.set_stretch(1, 3)
54
+
55
+ set_central_widget(centralwidget)
56
+ end
57
+
58
+ def _on_browse_action_triggered
59
+ dialog = BrowseStoresDialog.new
60
+ dialog.stores_changed.connect(self, :_on_browsestoresdialog_stores_changed)
61
+ dialog.show
62
+ end
63
+
64
+ def _on_browsestoresdialog_stores_changed
65
+ @passlistwidget.reinitialize_stores
66
+ end
67
+
68
+ def _on_passlistwidget_store_changed(store)
69
+ @passinfowidget.reinitialize_infoframe
70
+ end
71
+
72
+ def _on_passlistwidget_passfile_selected(store, passname)
73
+ @passinfowidget.reinitialize_passfile(store, passname)
74
+ end
75
+
76
+ def _on_passlistwidget_passfolder_selected(store, passname)
77
+ @passinfowidget.reinitialize_passfolder(store, passname)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,133 @@
1
+ module PassQt
2
+ class NewOneTimePasswordDialog < RubyQt6::Bando::QDialog
3
+ q_object do
4
+ slot "_on_okbutton_clicked()"
5
+ slot "_on_cancelbutton_clicked()"
6
+ end
7
+
8
+ def initialize(store, folder, on_success:)
9
+ super()
10
+
11
+ @store = store
12
+ @folder = folder
13
+ @on_success = on_success
14
+
15
+ initialize_form
16
+ initialize_btngroup
17
+
18
+ mainlayout = QVBoxLayout.new(self)
19
+ mainlayout.add_widget(@form)
20
+ mainlayout.add_widget(@btngroup)
21
+
22
+ set_attribute(Qt::WA_DeleteOnClose)
23
+ set_modal(true)
24
+ end
25
+
26
+ private
27
+
28
+ def initialize_form
29
+ titlelabel = QLabel.new("<center><b>Insert New OTP Password</b></center>")
30
+
31
+ @errinfolabel = QLabel.new("")
32
+ @errinfolabel.set_style_sheet("background: white; color: red; padding: 8px; margin: 4px;")
33
+ @errinfolabel.set_hidden(true)
34
+
35
+ placeholder = (@folder == "") ? "github.com-otp" : "#{@folder}/github.com-otp"
36
+ @passnamelabel = initialize_form_label("File")
37
+ @passnameinput = initialize_form_inputfield(placeholder)
38
+ set_focus_proxy(@passnameinput)
39
+
40
+ @passwordlabel = initialize_form_label("OTP URI")
41
+ @passwordinput = initialize_form_inputfield("otpauth://totp/GitHub...")
42
+
43
+ @form = QWidget.new
44
+ @form.set_object_name("newonetimepassworddialog_form")
45
+ @form.set_style_sheet("
46
+ #newonetimepassworddialog_form {
47
+ min-width: 600px;
48
+ }
49
+ ")
50
+
51
+ layout = QGridLayout.new(@form)
52
+ layout.add_widget(titlelabel, 0, 0, 1, 2)
53
+ layout.set_row_minimum_height(1, 10)
54
+ layout.add_widget(@errinfolabel, 2, 0, 1, 2)
55
+ layout.add_widget(@passnamelabel, 3, 0)
56
+ layout.add_widget(@passnameinput, 3, 1)
57
+ layout.add_widget(@passwordlabel, 4, 0)
58
+ layout.add_widget(@passwordinput, 4, 1)
59
+ end
60
+
61
+ def initialize_form_label(text)
62
+ label = QLabel.new(text)
63
+ label.set_alignment(Qt::AlignRight)
64
+ label.set_style_sheet("min-width: 80px; padding-right: 4px;")
65
+ label
66
+ end
67
+
68
+ def initialize_form_inputfield(placeholder)
69
+ input = QLineEdit.new
70
+ input.set_placeholder_text(placeholder)
71
+ input
72
+ end
73
+
74
+ def initialize_btngroup
75
+ @okbutton = QPushButton.new("Create")
76
+ @okbutton.clicked.connect(self, :_on_okbutton_clicked)
77
+
78
+ @cancelbutton = QPushButton.new("Cancel")
79
+ @cancelbutton.clicked.connect(self, :_on_cancelbutton_clicked)
80
+
81
+ @btngroup = QWidget.new
82
+ layout = QHBoxLayout.new(@btngroup)
83
+ layout.add_stretch
84
+ layout.add_widget(@okbutton)
85
+ layout.add_widget(@cancelbutton)
86
+ end
87
+
88
+ def update_form_errinfo(info)
89
+ if info.empty?
90
+ @errinfolabel.set_text("")
91
+ @errinfolabel.set_hidden(true)
92
+ return
93
+ end
94
+
95
+ @errinfolabel.set_text(info)
96
+ @errinfolabel.set_hidden(false)
97
+ end
98
+
99
+ def validate_form
100
+ update_form_errinfo("")
101
+
102
+ {
103
+ "File" => @passnameinput,
104
+ "OTP URI" => @passwordinput
105
+ }.each do |k, v|
106
+ next unless v.text.empty?
107
+
108
+ update_form_errinfo("#{k} can't be blank")
109
+ return false
110
+ end
111
+
112
+ true
113
+ end
114
+
115
+ def _on_okbutton_clicked
116
+ return unless validate_form
117
+
118
+ store = @store
119
+ passname = @passnameinput.text
120
+ password = @passwordinput.text
121
+ Pass.otp_insert(store, passname, password, on_success: ->(_) {
122
+ @on_success.call(passname)
123
+ close
124
+ }, on_failure: ->(data) {
125
+ update_form_errinfo(data["stderr"].strip)
126
+ })
127
+ end
128
+
129
+ def _on_cancelbutton_clicked
130
+ close
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,169 @@
1
+ module PassQt
2
+ class NewPasswordDialog < RubyQt6::Bando::QDialog
3
+ q_object do
4
+ slot "_on_okbutton_clicked()"
5
+ slot "_on_cancelbutton_clicked()"
6
+ slot "_on_view_action_triggered()"
7
+ end
8
+
9
+ def initialize(store, folder, on_success:)
10
+ super()
11
+
12
+ @store = store
13
+ @folder = folder
14
+ @on_success = on_success
15
+
16
+ initialize_form
17
+ initialize_btngroup
18
+
19
+ mainlayout = QVBoxLayout.new(self)
20
+ mainlayout.add_widget(@form)
21
+ mainlayout.add_widget(@btngroup)
22
+
23
+ set_attribute(Qt::WA_DeleteOnClose)
24
+ set_modal(true)
25
+ end
26
+
27
+ private
28
+
29
+ def initialize_form
30
+ titlelabel = QLabel.new("<center><b>Insert New Password</b></center>")
31
+
32
+ @errinfolabel = QLabel.new("")
33
+ @errinfolabel.set_style_sheet("background: white; color: red; padding: 8px; margin: 4px;")
34
+ @errinfolabel.set_hidden(true)
35
+
36
+ placeholder = (@folder == "") ? "github.com" : "#{@folder}/github.com"
37
+ @passnamelabel = initialize_form_label("File")
38
+ @passnameinput = initialize_form_inputfield(placeholder)
39
+ set_focus_proxy(@passnameinput)
40
+
41
+ @passwordlabel = initialize_form_label("Password")
42
+ @passwordinput = initialize_form_inputfield("")
43
+ initialize_form_inputfield_viewaction(@passwordinput)
44
+
45
+ @passwordinput.set_echo_mode(QLineEdit::Password)
46
+ Pass.pwgen(16, on_success: ->(data) {
47
+ @passwordinput.set_text(data["stdout"].strip)
48
+ }, on_failure: ->(_) {})
49
+
50
+ @usernamelabel = initialize_form_label("Username")
51
+ @usernameinput = initialize_form_inputfield("johndoe")
52
+
53
+ @websitelabel = initialize_form_label("Website")
54
+ @websiteinput = initialize_form_inputfield("https://github.com/login")
55
+
56
+ @form = QWidget.new
57
+ @form.set_object_name("newpassworddialog_form")
58
+ @form.set_style_sheet("
59
+ #newpassworddialog_form {
60
+ min-width: 600px;
61
+ }
62
+ ")
63
+
64
+ layout = QGridLayout.new(@form)
65
+ layout.add_widget(titlelabel, 0, 0, 1, 2)
66
+ layout.set_row_minimum_height(1, 10)
67
+ layout.add_widget(@errinfolabel, 2, 0, 1, 2)
68
+ layout.add_widget(@passnamelabel, 3, 0)
69
+ layout.add_widget(@passnameinput, 3, 1)
70
+ layout.add_widget(@usernamelabel, 4, 0)
71
+ layout.add_widget(@usernameinput, 4, 1)
72
+ layout.add_widget(@passwordlabel, 5, 0)
73
+ layout.add_widget(@passwordinput, 5, 1)
74
+ layout.add_widget(@websitelabel, 6, 0)
75
+ layout.add_widget(@websiteinput, 6, 1)
76
+ end
77
+
78
+ def initialize_form_label(text)
79
+ label = QLabel.new(text)
80
+ label.set_alignment(Qt::AlignRight)
81
+ label.set_style_sheet("min-width: 80px; padding-right: 4px;")
82
+ label
83
+ end
84
+
85
+ def initialize_form_inputfield(placeholder)
86
+ input = QLineEdit.new
87
+ input.set_placeholder_text(placeholder)
88
+ input
89
+ end
90
+
91
+ def initialize_form_inputfield_viewaction(input)
92
+ action = input.add_action(QIcon.from_theme(QIcon::ThemeIcon::DocumentPrintPreview), QLineEdit::TrailingPosition)
93
+ action.triggered.connect(self, :_on_view_action_triggered)
94
+ end
95
+
96
+ def initialize_btngroup
97
+ @okbutton = QPushButton.new("Create")
98
+ @okbutton.clicked.connect(self, :_on_okbutton_clicked)
99
+
100
+ @cancelbutton = QPushButton.new("Cancel")
101
+ @cancelbutton.clicked.connect(self, :_on_cancelbutton_clicked)
102
+
103
+ @btngroup = QWidget.new
104
+ layout = QHBoxLayout.new(@btngroup)
105
+ layout.add_stretch
106
+ layout.add_widget(@okbutton)
107
+ layout.add_widget(@cancelbutton)
108
+ end
109
+
110
+ def update_form_errinfo(info)
111
+ if info.empty?
112
+ @errinfolabel.set_text("")
113
+ @errinfolabel.set_hidden(true)
114
+ return
115
+ end
116
+
117
+ @errinfolabel.set_text(info)
118
+ @errinfolabel.set_hidden(false)
119
+ end
120
+
121
+ def validate_form
122
+ update_form_errinfo("")
123
+
124
+ {
125
+ "File" => @passnameinput,
126
+ "Username" => @usernameinput,
127
+ "Password" => @passwordinput,
128
+ "Website" => @websiteinput
129
+ }.each do |k, v|
130
+ next unless v.text.empty?
131
+
132
+ update_form_errinfo("#{k} can't be blank")
133
+ return false
134
+ end
135
+
136
+ true
137
+ end
138
+
139
+ def _on_okbutton_clicked
140
+ return unless validate_form
141
+
142
+ store = @store
143
+ passname = @passnameinput.text
144
+ password = @passwordinput.text
145
+ username = @usernameinput.text
146
+ website = @websiteinput.text
147
+ extra = "Username: #{username}\nWebsite: #{website}\n"
148
+ Pass.insert(store, passname, password, extra, on_success: ->(_) {
149
+ @on_success.call(passname)
150
+ close
151
+ }, on_failure: ->(data) {
152
+ update_form_errinfo(data["stderr"].strip)
153
+ })
154
+ end
155
+
156
+ def _on_cancelbutton_clicked
157
+ close
158
+ end
159
+
160
+ def _on_view_action_triggered
161
+ input = sender.parent
162
+ case input.echo_mode
163
+ when QLineEdit::Normal then input.set_echo_mode(QLineEdit::Password)
164
+ when QLineEdit::Password then input.set_echo_mode(QLineEdit::Normal)
165
+ else raise "unreachable!"
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,4 @@
1
+ require_relative "views/browsestoresdialog"
2
+ require_relative "views/newonetimepassworddialog"
3
+ require_relative "views/newpassworddialog"
4
+ require_relative "views/mainwindow"
@@ -0,0 +1,23 @@
1
+ require_relative "config"
2
+ require_relative "lib"
3
+
4
+ require_relative "app/components"
5
+ require_relative "app/views"
6
+
7
+ module PassQt
8
+ class Application
9
+ def self.run
10
+ new.exec
11
+ end
12
+
13
+ def initialize
14
+ @app = QApplication.new
15
+ @mainwindow = MainWindow.new
16
+ end
17
+
18
+ def exec
19
+ @mainwindow.show
20
+ @app.exec
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,2 @@
1
+ QApplication.set_organization_name("souk4711")
2
+ QApplication.set_application_name("PassQt")
@@ -0,0 +1,40 @@
1
+ module PassQt
2
+ class Settings
3
+ def GET_mainwindow_geometry_and_restore_to(mainwindow)
4
+ settings = QSettings.new
5
+ mainwindow.restore_geometry(settings.value("MainWindow/geometry", QByteArray.new("")))
6
+ mainwindow.restore_state(settings.value("MainWindow/windowState", QByteArray.new("")))
7
+ end
8
+
9
+ def PUT_mainwindow_geometry(mainwindow)
10
+ settings = QSettings.new
11
+ settings.set_value("MainWindow/geometry", mainwindow.save_geometry)
12
+ settings.set_value("MainWindow/windowState", mainwindow.save_state)
13
+ end
14
+
15
+ def GET_stores
16
+ settings = QSettings.new
17
+ qstringlist = settings.value("Store/used", QStringList.new)
18
+ if qstringlist.empty?
19
+ default_stores.tap { |stores| PUT_stores(stores) }
20
+ else
21
+ qstringlist.map { |qstring| JSON.parse(qstring) }
22
+ end
23
+ end
24
+
25
+ def PUT_stores(stores)
26
+ settings = QSettings.new
27
+ settings.set_value("Store/used", QStringList.new.tap do |qstringlist|
28
+ stores.each { |store| qstringlist << store.to_json }
29
+ end)
30
+ end
31
+
32
+ private
33
+
34
+ def default_stores
35
+ [] << {
36
+ "fullpath" => QDir.home.file_path(".password-store")
37
+ }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "config/initializers/qt6"
2
+
3
+ require_relative "config/settings"
4
+
5
+ module PassQt
6
+ def self.settings
7
+ @settings ||= Settings.new
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ module Contrib
2
+ class Process < RubyQt6::Bando::QObject
3
+ q_object do
4
+ slot "_on_process_error_occurred(QProcess::ProcessError)"
5
+ slot "_on_process_finished(int,QProcess::ExitStatus)"
6
+ end
7
+
8
+ def self.execute(program, arguments, **kwargs)
9
+ new._execute(program, arguments, **kwargs)
10
+ end
11
+
12
+ def _execute(program, arguments, stdin: nil, envs: nil, on_success: nil, on_failure: nil)
13
+ process = QProcess.new
14
+ @on_success = on_success
15
+ @on_failure = on_failure
16
+
17
+ if envs
18
+ process_env = QProcessEnvironment.system_environment
19
+ envs.each { |k, v| process_env.insert(k.to_s, v.to_s) }
20
+ process.set_process_environment(process_env)
21
+ end
22
+
23
+ process.start(program, arguments)
24
+ process.write(stdin.to_s) if stdin
25
+ process.close_write_channel
26
+ process.error_occurred.connect(self, :_on_process_error_occurred)
27
+ process.finished.connect(self, :_on_process_finished)
28
+ process
29
+ end
30
+
31
+ private
32
+
33
+ def _on_process_error_occurred(error)
34
+ process = sender
35
+ data = {"code" => nil, "stdout" => "", "stderr" => error.to_s}
36
+ @on_failure&.call(data)
37
+ ensure
38
+ process.delete_later
39
+ end
40
+
41
+ def _on_process_finished(code, status)
42
+ process = sender
43
+ stdout = process.read_all_standard_output.to_s
44
+ stderr = process.read_all_standard_error.to_s
45
+ data = {"code" => code, "stdout" => stdout, "stderr" => stderr}
46
+
47
+ if status != QProcess::NormalExit
48
+ @on_failure&.call(data)
49
+ elsif code != 0
50
+ @on_failure&.call(data)
51
+ else
52
+ @on_success&.call(data)
53
+ end
54
+ ensure
55
+ process.delete_later
56
+ end
57
+ end
58
+ end
@@ -0,0 +1 @@
1
+ require_relative "contrib/process"
@@ -0,0 +1,32 @@
1
+ module Pass
2
+ def self.show(store, passname, on_success:, on_failure:)
3
+ arguments = QStringList.new << "show" << passname
4
+ envs = {PASSWORD_STORE_DIR: store}
5
+ Contrib::Process.execute("pass", arguments, envs:, on_success:, on_failure:)
6
+ end
7
+
8
+ def self.insert(store, passname, password, extra, on_success:, on_failure:)
9
+ arguments = QStringList.new << "insert" << "-m" << passname
10
+ stdin = "#{password}\n#{extra}"
11
+ envs = {PASSWORD_STORE_DIR: store}
12
+ Contrib::Process.execute("pass", arguments, stdin:, envs:, on_success:, on_failure:)
13
+ end
14
+
15
+ def self.otp(store, passname, on_success:, on_failure:)
16
+ arguments = QStringList.new << "otp" << passname
17
+ envs = {PASSWORD_STORE_DIR: store}
18
+ Contrib::Process.execute("pass", arguments, envs:, on_success:, on_failure:)
19
+ end
20
+
21
+ def self.otp_insert(store, passname, password, on_success:, on_failure:)
22
+ arguments = QStringList.new << "otp" << "insert" << passname
23
+ stdin = password
24
+ envs = {PASSWORD_STORE_DIR: store}
25
+ Contrib::Process.execute("pass", arguments, stdin:, envs:, on_success:, on_failure:)
26
+ end
27
+
28
+ def self.pwgen(pw_length, on_success:, on_failure:)
29
+ arguments = QStringList.new << "-cnysB" << pw_length.to_s << "1"
30
+ Contrib::Process.execute("pwgen", arguments, on_success:, on_failure:)
31
+ end
32
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "lib/contrib"
2
+ require_relative "lib/pass"
@@ -0,0 +1,3 @@
1
+ module PassQt
2
+ VERSION = "1.0.0"
3
+ end
data/lib/pass-qt.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "json"
2
+
3
+ require "qt6/qtwidgets"
4
+
5
+ require_relative "pass-qt/app"
6
+ require_relative "pass-qt/version"
data/screenshot.png ADDED
Binary file