KeyboardPaster 0.1.16__tar.gz → 0.1.18__tar.gz

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.
Files changed (19) hide show
  1. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/KeyboardPaster.egg-info/PKG-INFO +8 -3
  2. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/PKG-INFO +8 -3
  3. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/README.md +7 -2
  4. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/keyboardpaster/app.py +189 -34
  5. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/keyboardpaster/keyboardpaster_app.kv +49 -12
  6. keyboardpaster-0.1.18/keyboardpaster/shared_resources.py +1 -0
  7. keyboardpaster-0.1.16/keyboardpaster/shared_resources.py +0 -1
  8. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/KeyboardPaster.egg-info/SOURCES.txt +0 -0
  9. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/KeyboardPaster.egg-info/dependency_links.txt +0 -0
  10. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/KeyboardPaster.egg-info/entry_points.txt +0 -0
  11. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/KeyboardPaster.egg-info/requires.txt +0 -0
  12. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/KeyboardPaster.egg-info/top_level.txt +0 -0
  13. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/LICENSE +0 -0
  14. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/keyboardpaster/__init__.py +0 -0
  15. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/keyboardpaster/keyboard_layout_detector.py +0 -0
  16. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/keyboardpaster/modules/__init__.py +0 -0
  17. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/keyboardpaster/modules/autoupdate.py +0 -0
  18. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/setup.cfg +0 -0
  19. {keyboardpaster-0.1.16 → keyboardpaster-0.1.18}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: KeyboardPaster
3
- Version: 0.1.16
3
+ Version: 0.1.18
4
4
  Summary: A Python package to type text using the keyboard module
5
5
  Home-page: https://github.com/KnutssonDevelopment/KeyboardPaster
6
6
  Author: Brian Knutsson
@@ -37,10 +37,15 @@ A Python package to type text using the keyboard module. This is useful in situa
37
37
 
38
38
  ## Future features
39
39
  - Paste images as base64 code
40
- - Enter would activate the Paste operation
41
- - Shift+Enter would move all inputs downwards and put the cursor into the next, now empty, field
42
40
 
43
41
  ## Changes
42
+ ### v0.1.18
43
+ - FEATURE: Added a possibility to have multiple profiles.
44
+
45
+ ### v0.1.17
46
+ - FEATURE: Added a button to put text into clipboard
47
+ - UPDATE: Changed the keyboard paste option into an icon
48
+
44
49
  ### v0.1.16
45
50
  - FEATURE: Changes the launcher to run as a gui application and not show the command line when executed
46
51
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: KeyboardPaster
3
- Version: 0.1.16
3
+ Version: 0.1.18
4
4
  Summary: A Python package to type text using the keyboard module
5
5
  Home-page: https://github.com/KnutssonDevelopment/KeyboardPaster
6
6
  Author: Brian Knutsson
@@ -37,10 +37,15 @@ A Python package to type text using the keyboard module. This is useful in situa
37
37
 
38
38
  ## Future features
39
39
  - Paste images as base64 code
40
- - Enter would activate the Paste operation
41
- - Shift+Enter would move all inputs downwards and put the cursor into the next, now empty, field
42
40
 
43
41
  ## Changes
42
+ ### v0.1.18
43
+ - FEATURE: Added a possibility to have multiple profiles.
44
+
45
+ ### v0.1.17
46
+ - FEATURE: Added a button to put text into clipboard
47
+ - UPDATE: Changed the keyboard paste option into an icon
48
+
44
49
  ### v0.1.16
45
50
  - FEATURE: Changes the launcher to run as a gui application and not show the command line when executed
46
51
 
@@ -8,10 +8,15 @@ A Python package to type text using the keyboard module. This is useful in situa
8
8
 
9
9
  ## Future features
10
10
  - Paste images as base64 code
11
- - Enter would activate the Paste operation
12
- - Shift+Enter would move all inputs downwards and put the cursor into the next, now empty, field
13
11
 
14
12
  ## Changes
13
+ ### v0.1.18
14
+ - FEATURE: Added a possibility to have multiple profiles.
15
+
16
+ ### v0.1.17
17
+ - FEATURE: Added a button to put text into clipboard
18
+ - UPDATE: Changed the keyboard paste option into an icon
19
+
15
20
  ### v0.1.16
16
21
  - FEATURE: Changes the launcher to run as a gui application and not show the command line when executed
17
22
 
@@ -2,6 +2,8 @@ import time
2
2
  import re
3
3
  import json
4
4
  import pkg_resources
5
+ import subprocess
6
+
5
7
  from pynput.keyboard import Controller, Key
6
8
 
7
9
  from kivymd.app import MDApp
@@ -11,6 +13,9 @@ from kivy.lang import Builder
11
13
  from kivy.clock import Clock
12
14
  from kivy.core.window import Window
13
15
  from kivymd.uix.selectioncontrol.selectioncontrol import MDCheckbox
16
+ from kivymd.uix.button.button import MDRaisedButton
17
+ from kivymd.uix.dialog import MDDialog
18
+ from kivymd.uix.textfield import MDTextField
14
19
 
15
20
  from keyboardpaster.keyboard_layout_detector import get_keyboard_layout
16
21
  from keyboardpaster.modules.autoupdate import autoupdate
@@ -124,13 +129,21 @@ class KeyboardPasterApp(MDApp):
124
129
  start_delay = ObjectProperty(None)
125
130
  layout = 'EN_US'
126
131
 
132
+ def __init__(self):
133
+ super().__init__()
134
+ self.config = None
135
+ self.json_config = 'saved_inputs.json'
136
+
127
137
  def build(self):
128
138
  self.theme_cls.primary_palette = "DeepPurple" # Change to your desired primary color
129
139
  self.theme_cls.accent_palette = "Amber" # Change to your desired accent color
130
140
  self.theme_cls.theme_style = "Light" # Set the theme to either "Light" or "Dark"
131
141
 
142
+ self.read_config()
143
+
132
144
  self.detect_keyboard_layout()
133
- Clock.schedule_once(self.load_inputs, 1)
145
+ #Clock.schedule_once(self.load_inputs, 1)
146
+ Clock.schedule_once(self.set_profile, 1)
134
147
  self.title = f"Keyboard Paster v{app_version}"
135
148
  Window.size = (1000, 700)
136
149
 
@@ -138,49 +151,182 @@ class KeyboardPasterApp(MDApp):
138
151
  return Builder.load_file(kv_file_path)
139
152
 
140
153
  def on_stop(self):
141
- # self.save_inputs()
142
154
  pass
143
155
 
144
- def load_inputs(self, dt):
156
+ def read_config(self):
145
157
  try:
146
- with open("saved_inputs.json", "r") as file:
147
- saved_inputs = json.load(file)
158
+ with open(self.json_config, "r", encoding='utf-8') as file:
159
+ self.config = json.load(file)
160
+ except FileNotFoundError:
161
+ pass
162
+ except AttributeError:
163
+ pass
164
+ except json.JSONDecodeError:
165
+ pass
166
+ except ValueError:
167
+ pass
168
+ except KeyError:
169
+ pass
170
+
171
+ if 'profiles' in self.config:
172
+ pass
173
+ elif 'input_text_0' in self.config:
174
+ values = self.config
175
+ self.config = dict()
176
+ self.config['profiles'] = dict()
177
+ self.config['profiles']['DEFAULT'] = values
178
+ else:
179
+ self.config = dict()
180
+ self.config['profiles'] = dict()
181
+ self.config['profiles']['DEFAULT'] = dict()
182
+
183
+ def write_config(self):
184
+ with open(self.json_config, "w", encoding='utf-8') as file:
185
+ json.dump(self.config, file)
186
+
187
+ @staticmethod
188
+ def close_dialog(button_instance):
189
+ # Navigate up the widget tree until we find an MDDialog instance
190
+ current_widget = button_instance
191
+ while current_widget and not isinstance(current_widget, MDDialog):
192
+ current_widget = current_widget.parent
193
+
194
+ # If we found an MDDialog instance, dismiss it
195
+ if isinstance(current_widget, MDDialog):
196
+ current_widget.dismiss()
197
+
198
+ def add_profile(self, profile_name, button_instance):
199
+ # Access the text from the input box
200
+ print(f"New profile name: {profile_name}")
201
+ self.config['profiles'][profile_name] = dict()
202
+ self.set_profile(None, profile_name)
203
+
204
+ # Close the dialog using the button instance
205
+ self.close_dialog(button_instance)
206
+
207
+ def create_new_profile(self):
208
+ self.close_dropdown()
209
+
210
+ profile_input = MDTextField(
211
+ hint_text="Enter profile name",
212
+ size_hint_x=0.8,
213
+ pos_hint={"center_x": 0.5}
214
+ )
215
+
216
+ dialog = MDDialog(
217
+ title="New Profile",
218
+ type="custom",
219
+ content_cls=profile_input,
220
+ buttons=[
221
+ MDRaisedButton(
222
+ text="CANCEL",
223
+ on_release=self.close_dialog
224
+ ),
225
+ MDRaisedButton(
226
+ text="OK",
227
+ on_release=lambda x: self.add_profile(profile_input.text, x)
228
+ )
229
+ ],
230
+ )
231
+ dialog.open()
232
+
233
+ def close_dropdown(self):
234
+ profile_dropdown = self.root.ids['profile_dropdown']
235
+ profile_dropdown.dismiss()
236
+
237
+ def handle_dropdown(self, profile):
238
+ self.set_profile(None, profile)
239
+ self.close_dropdown()
240
+
241
+ def generate_profile_selector(self):
242
+ profile_dropdown = self.root.ids['profile_dropdown']
243
+ profile_selector = self.root.ids['profile_selector']
244
+ try:
245
+ profiles = list(self.config['profiles'].keys())
246
+ except KeyError:
247
+ profiles = ['DEFAULT']
248
+ max_length = 7
249
+
250
+ # Removing existing widgets
251
+ for child in profile_dropdown.children:
252
+ for widget in reversed(child.children):
253
+ child.remove_widget(widget)
254
+
255
+ btn = MDRaisedButton()
256
+ btn.text = '- New -'
257
+ btn.size_hint_y = None
258
+ btn.bind(on_release=lambda x: self.create_new_profile())
259
+ profile_dropdown.add_widget(btn)
260
+
261
+ for profile in profiles:
262
+ if len(profile) > max_length:
263
+ max_length = len(profile)
264
+
265
+ if profile != profile_selector.text.strip():
266
+ btn = MDRaisedButton()
267
+ btn.text = profile
268
+ btn.size_hint_y = None
269
+ btn.bind(on_release=lambda x, p=profile: self.handle_dropdown(p))
270
+ profile_dropdown.add_widget(btn)
271
+
272
+ number_of_whitespaces = max_length - len(profile_selector.text.strip())
273
+ profile_selector.text = profile_selector.text + (number_of_whitespaces + 1) * " "
274
+
275
+ def set_profile(self, dt=None, profile=None):
276
+ profile_selector = self.root.ids['profile_selector']
277
+ profile_name = "DEFAULT"
278
+ saved_inputs = list()
279
+
280
+ if profile is None:
281
+ profile_name = list(self.config['profiles'].keys())[0]
282
+ else:
283
+ profile_name = profile
284
+
285
+ profile_selector.text = profile_name
286
+ self.load_inputs()
287
+ self.generate_profile_selector()
148
288
 
149
- input_field_buttons = sum([x.children for x in self.root.ids['input_fields_container'].children], [])
150
- input_fields = [x for x in input_field_buttons if isinstance(x, TextInput)]
151
- checkboxes = [x for x in input_field_buttons if isinstance(x, MDCheckbox) and getattr(x, 'secret_checkbox', False)]
289
+ def load_inputs(self):
290
+ profile_selector = self.root.ids['profile_selector']
291
+ profile_name = profile_selector.text.strip()
152
292
 
153
- for name, (text, secret_state) in saved_inputs.items():
293
+ if profile_name in self.config['profiles']:
294
+ profile_values = self.config['profiles'][profile_name]
295
+ else:
296
+ profile_values = dict()
297
+
298
+ input_field_buttons = sum([x.children for x in self.root.ids['input_fields_container'].children], [])
299
+ input_fields = [x for x in input_field_buttons if isinstance(x, TextInput)]
300
+ checkboxes = [x for x in input_field_buttons if isinstance(x, MDCheckbox) and getattr(x, 'secret_checkbox', False)]
301
+
302
+ if len(profile_values.items()) > 1:
303
+ for name, (text, secret_state) in profile_values.items():
154
304
  # Set text for TextInput
155
305
  for input_field in input_fields:
156
306
  if input_field.parent.text_input_id == name:
157
307
  input_field.text = text
158
308
  break # Found the matching input field, no need to continue the loop
159
309
 
160
- # Set state for corresponding checkbox and adjust text visibility
161
- for checkbox in checkboxes:
162
- if checkbox.parent.text_input_id == name:
163
- checkbox.active = secret_state
164
- # Assuming MDTextField is a sibling or accessible as input_field here
165
- # and setting its "password" property based on checkbox state
166
- if secret_state:
167
- input_field.password = True # Hide text if checkbox is checked
168
- else:
169
- input_field.password = False # Show text otherwise
170
- break # Found the matching checkbox, no need to continue the loop
310
+ # Set state for corresponding checkbox and adjust text visibility
311
+ for checkbox in checkboxes:
312
+ if checkbox.parent.text_input_id == name:
313
+ checkbox.active = secret_state
314
+ # Assuming MDTextField is a sibling or accessible as input_field here
315
+ # and setting its "password" property based on checkbox state
316
+ if secret_state:
317
+ input_field.password = True # Hide text if checkbox is checked
318
+ else:
319
+ input_field.password = False # Show text otherwise
320
+ break # Found the matching checkbox, no need to continue the loop
321
+ else:
322
+ for input_field in input_fields:
323
+ input_field.text = text = ""
171
324
 
172
- except FileNotFoundError:
173
- pass
174
- except AttributeError:
175
- pass
176
- except json.JSONDecodeError:
177
- # Handle cases where the JSON file is empty or corrupted
178
- pass
179
- except ValueError:
180
- # Handle cases where the JSON file is empty or corrupted
181
- pass
182
325
 
183
326
  def save_inputs(self):
327
+ profile_selector = self.root.ids['profile_selector']
328
+ profile_name = profile_selector.text.strip()
329
+
184
330
  # Assuming `input_field_buttons` contains all relevant child widgets,
185
331
  # including both TextInput and MDCheckbox widgets.
186
332
  input_field_buttons = sum([x.children for x in self.root.ids['input_fields_container'].children], [])
@@ -201,11 +347,11 @@ class KeyboardPasterApp(MDApp):
201
347
  # Store the tuple of text and secret_state in the dictionary.
202
348
  input_fields[child.parent.text_input_id] = (child.text, secret_state)
203
349
 
204
- # Save the dictionary to a JSON file.
205
- with open("saved_inputs.json", "w") as file:
206
- json.dump(input_fields, file)
207
350
 
208
- def paste_text(self, input_text, _checkbox):
351
+ self.config['profiles'][profile_name] = input_fields
352
+ self.write_config()
353
+
354
+ def type_text(self, input_text, _checkbox):
209
355
  if not input_text:
210
356
  # print("No text found")
211
357
  return
@@ -219,6 +365,15 @@ class KeyboardPasterApp(MDApp):
219
365
  mod_delay = float(self.root.ids["mod_delay"].value)
220
366
  type_string_with_delay(input_text, start_delay=start_delay, mod_delay=mod_delay, layout=self.layout, end_line=end_line)
221
367
 
368
+ @staticmethod
369
+ def copy_text(input_text, _checkbox):
370
+ if not input_text:
371
+ # print("No text found")
372
+ return
373
+
374
+ cmd = 'echo ' + input_text.strip() + '|clip'
375
+ subprocess.check_call(cmd, shell=True)
376
+
222
377
  @staticmethod
223
378
  def hide_text(_input_text, _checkbox):
224
379
  if _checkbox.state == "down":
@@ -28,22 +28,58 @@
28
28
  text: "Secret"
29
29
  size_hint: 0.1, 1
30
30
 
31
- MDRaisedButton:
32
- text: "Paste"
33
- on_press: app.paste_text(_text_input.text, _cb_execute)
31
+ MDIconButton:
32
+ icon: "keyboard"
33
+ on_press: app.type_text(_text_input.text, _cb_execute)
34
34
 
35
+ MDIconButton:
36
+ icon: "content-copy"
37
+ on_press: app.copy_text(_text_input.text, _cb_execute)
38
+
39
+ <ProfileButton@MDFlatButton>:
40
+ text: "DEFAULT"
41
+ size_hint_y: None
42
+ height: 44
43
+ on_release:
44
+ root.select(self.text)
45
+
46
+ <CustomDropDown@DropDown>:
35
47
 
36
48
  MDBoxLayout:
37
49
  orientation: "vertical"
38
50
 
39
51
  MDTopAppBar:
40
- MDLabel:
41
- id: title_bar
42
- text: "Keyboard Paster"
43
- halign: "center"
44
- theme_text_color: "Custom"
45
- text_color: 1, 1, 1, 1
46
- font_size: '30sp'
52
+ MDBoxLayout:
53
+ orientation: "horizontal"
54
+
55
+ MDLabel:
56
+ id: title_bar
57
+ text: "Keyboard Paster"
58
+ halign: "center"
59
+ theme_text_color: "Custom"
60
+ text_color: 1, 1, 1, 1
61
+ font_size: '30sp'
62
+
63
+ MDBoxLayout:
64
+ orientation: "horizontal"
65
+ size_hint_x: .3
66
+
67
+ MDLabel:
68
+ text: "Profile:"
69
+ halign: 'right'
70
+ theme_text_color: "Custom"
71
+ text_color: 1, 1, 1, 1
72
+ font_size: '22sp'
73
+
74
+ MDFlatButton:
75
+ id: profile_selector
76
+ text: "DEFAULT"
77
+ pos_hint: {'center_y': 0.5}
78
+ theme_text_color: "Custom"
79
+ text_color: 1, 1, 1, 1
80
+ font_size: '22sp'
81
+ on_parent: profile_dropdown.dismiss()
82
+ on_release: profile_dropdown.open(self)
47
83
 
48
84
  MDBoxLayout:
49
85
  orientation: "vertical"
@@ -129,8 +165,9 @@ MDBoxLayout:
129
165
  text: str(round(mod_delay.value, 1)) + " seconds"
130
166
  theme_text_color: "Secondary"
131
167
 
132
-
133
-
134
168
  MDRaisedButton:
135
169
  text: "Save Inputs"
136
170
  on_release: app.save_inputs()
171
+
172
+ CustomDropDown:
173
+ id: profile_dropdown
@@ -0,0 +1 @@
1
+ app_version = "0.1.18"
@@ -1 +0,0 @@
1
- app_version = "0.1.16"
File without changes