SeedShield 0.2.1__tar.gz → 0.2.2__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 (33) hide show
  1. {seedshield-0.2.1/SeedShield.egg-info → seedshield-0.2.2}/PKG-INFO +1 -1
  2. {seedshield-0.2.1 → seedshield-0.2.2/SeedShield.egg-info}/PKG-INFO +1 -1
  3. {seedshield-0.2.1 → seedshield-0.2.2}/pyproject.toml +1 -1
  4. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/config.py +1 -1
  5. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/display_handler.py +17 -8
  6. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/input_handler.py +58 -29
  7. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/secure_word_interface.py +14 -7
  8. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/state_handler.py +3 -1
  9. {seedshield-0.2.1 → seedshield-0.2.2}/setup.py +1 -1
  10. {seedshield-0.2.1 → seedshield-0.2.2}/LICENSE +0 -0
  11. {seedshield-0.2.1 → seedshield-0.2.2}/MANIFEST.in +0 -0
  12. {seedshield-0.2.1 → seedshield-0.2.2}/README.md +0 -0
  13. {seedshield-0.2.1 → seedshield-0.2.2}/SeedShield.egg-info/SOURCES.txt +0 -0
  14. {seedshield-0.2.1 → seedshield-0.2.2}/SeedShield.egg-info/dependency_links.txt +0 -0
  15. {seedshield-0.2.1 → seedshield-0.2.2}/SeedShield.egg-info/entry_points.txt +0 -0
  16. {seedshield-0.2.1 → seedshield-0.2.2}/SeedShield.egg-info/requires.txt +0 -0
  17. {seedshield-0.2.1 → seedshield-0.2.2}/SeedShield.egg-info/top_level.txt +0 -0
  18. {seedshield-0.2.1 → seedshield-0.2.2}/pre_build.py +0 -0
  19. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/__init__.py +0 -0
  20. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/data/english.txt +0 -0
  21. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/main.py +0 -0
  22. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/secure_memory.py +0 -0
  23. {seedshield-0.2.1 → seedshield-0.2.2}/seedshield/ui_manager.py +0 -0
  24. {seedshield-0.2.1 → seedshield-0.2.2}/setup.cfg +0 -0
  25. {seedshield-0.2.1 → seedshield-0.2.2}/tests/__init__.py +0 -0
  26. {seedshield-0.2.1 → seedshield-0.2.2}/tests/conftest.py +0 -0
  27. {seedshield-0.2.1 → seedshield-0.2.2}/tests/test_display_handler.py +0 -0
  28. {seedshield-0.2.1 → seedshield-0.2.2}/tests/test_fixtures.py +0 -0
  29. {seedshield-0.2.1 → seedshield-0.2.2}/tests/test_input_handler.py +0 -0
  30. {seedshield-0.2.1 → seedshield-0.2.2}/tests/test_main.py +0 -0
  31. {seedshield-0.2.1 → seedshield-0.2.2}/tests/test_secure_word_interface.py +0 -0
  32. {seedshield-0.2.1 → seedshield-0.2.2}/tests/test_state_handler.py +0 -0
  33. {seedshield-0.2.1 → seedshield-0.2.2}/tests/test_ui_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: SeedShield
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Secure BIP39 word viewer with masking and reveal functionality
5
5
  Author-email: Barlog951 <barlog951@gmail.com>
6
6
  Project-URL: Documentation, https://github.com/Barlog951/SeedShield/blob/main/README.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: SeedShield
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Secure BIP39 word viewer with masking and reveal functionality
5
5
  Author-email: Barlog951 <barlog951@gmail.com>
6
6
  Project-URL: Documentation, https://github.com/Barlog951/SeedShield/blob/main/README.md
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "SeedShield"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  readme = { file = "README.md", content-type = "text/markdown" }
9
9
  description = "Secure BIP39 word viewer with masking and reveal functionality"
10
10
  authors = [
@@ -12,7 +12,7 @@ import sys
12
12
 
13
13
  # Application constants
14
14
  APP_NAME = "SeedShield"
15
- VERSION = "0.2.1"
15
+ VERSION = "0.2.2"
16
16
 
17
17
  # Security settings
18
18
  REVEAL_TIMEOUT = 3 # Seconds before auto-hiding revealed words
@@ -10,6 +10,7 @@ from typing import List, Optional, Any
10
10
 
11
11
  from .config import logger, MASK_CHARACTER, MASK_LENGTH
12
12
  from .config import SCROLL_INDICATOR_UP, SCROLL_INDICATOR_DOWN, MENU_TEXT
13
+ from .state_handler import StateHandler
13
14
 
14
15
 
15
16
  class DisplayHandler:
@@ -35,6 +36,7 @@ class DisplayHandler:
35
36
  self.ui_manager = ui_manager
36
37
  self.mask = MASK_CHARACTER * MASK_LENGTH
37
38
  self.legacy_mask = self.mask # For backward compatibility with tests
39
+ self.state_handler: Optional[StateHandler] = None # Will be set later
38
40
 
39
41
  # Store the last state to optimize rendering
40
42
  self._last_height = 0
@@ -42,8 +44,10 @@ class DisplayHandler:
42
44
 
43
45
  logger.debug("DisplayHandler initialized with %s words", len(words))
44
46
 
45
- def _add_scroll_indicators(self, stdscr: Any, visible_start: int, visible_end: int,
46
- positions: List[int], height: int, width: int) -> None:
47
+ def _add_scroll_indicators(
48
+ self, stdscr: Any, visible_start: int, visible_end: int,
49
+ positions: List[int], height: int, width: int
50
+ ) -> None:
47
51
  """
48
52
  Add scroll indicators to the display if needed.
49
53
 
@@ -94,6 +98,7 @@ class DisplayHandler:
94
98
 
95
99
  # Show reset option only if we've reached the last word
96
100
  menu_text = MENU_TEXT["with_reset"] if is_last_reached else MENU_TEXT["standard"]
101
+ # We no longer show the wordlist info on any screen to avoid blocking words
97
102
 
98
103
  # Split menu text across lines if it's too long
99
104
  if len(menu_text) > self._last_width - 2:
@@ -168,8 +173,10 @@ class DisplayHandler:
168
173
  logger.warning("Invalid word position: %s", pos)
169
174
  return f"INVALID({pos})"
170
175
 
171
- def display_words(self, stdscr: Any, positions: List[int], scroll_position: int,
172
- cursor_pos: Optional[int], is_last_reached: bool) -> int:
176
+ def display_words(
177
+ self, stdscr: Any, positions: List[int], scroll_position: int,
178
+ cursor_pos: Optional[int], is_last_reached: bool
179
+ ) -> int:
173
180
  """
174
181
  Display words with masking in the terminal interface.
175
182
 
@@ -213,10 +220,12 @@ class DisplayHandler:
213
220
  result: int = visible_end - visible_start
214
221
  return result
215
222
 
216
- def _display_visible_words(self, stdscr: Any, positions: List[int],
217
- visible_start: int, visible_end: int,
218
- scroll_position: int, cursor_pos: Optional[int],
219
- height: int, width: int) -> None:
223
+ def _display_visible_words(
224
+ self, stdscr: Any, positions: List[int],
225
+ visible_start: int, visible_end: int,
226
+ scroll_position: int, cursor_pos: Optional[int],
227
+ height: int, width: int
228
+ ) -> None:
220
229
  """
221
230
  Display the visible words on the screen.
222
231
 
@@ -187,6 +187,50 @@ class InputHandler:
187
187
  logger.error("Unexpected error reading positions file: %s", str(e))
188
188
  return None
189
189
 
190
+ def _display_error(self, stdscr: Any, message: str, error: Optional[Exception] = None) -> None:
191
+ """
192
+ Display an error message on the screen.
193
+
194
+ Args:
195
+ stdscr: Curses window object
196
+ message: Error message to display
197
+ error: Optional exception to log
198
+ """
199
+ if error:
200
+ logger.error("%s: %s", message, str(error))
201
+ stdscr.addstr(6, 0, message)
202
+ stdscr.refresh()
203
+ time.sleep(1)
204
+
205
+ def _process_input_command(self, stdscr: Any, input_str: str) -> Optional[List[int]]:
206
+ """
207
+ Process a specific input command.
208
+
209
+ Args:
210
+ stdscr: Curses window object
211
+ input_str: User input string
212
+
213
+ Returns:
214
+ Optional[List[int]]: Processed positions or None
215
+ """
216
+ # Handle quitting
217
+ if input_str == 'q':
218
+ return None
219
+
220
+ # Handle clipboard input
221
+ if input_str == 'v':
222
+ return self.process_clipboard_input(stdscr)
223
+
224
+ # Handle individual number
225
+ validated_input = self.validate_number_input(input_str)
226
+ if validated_input:
227
+ return validated_input
228
+
229
+ # Invalid input
230
+ err_msg = f"Invalid input. Please enter a number between 1-{self.word_count}"
231
+ self._display_error(stdscr, err_msg)
232
+ return [] # Empty list indicates continuing input loop
233
+
190
234
  def get_input(self, stdscr: Any) -> Optional[List[int]]:
191
235
  """
192
236
  Get user input, validating it and handling different input types.
@@ -198,45 +242,30 @@ class InputHandler:
198
242
  Optional[List[int]]: List of valid position numbers or None for quit command
199
243
  """
200
244
  while True:
245
+ # Clear any previous error messages
246
+ stdscr.move(6, 0)
247
+ stdscr.clrtoeol()
201
248
  self.display_input_prompt(stdscr)
202
249
  curses.echo()
203
250
 
204
251
  try:
205
252
  input_str = stdscr.getstr().decode('utf-8').strip().lower()
253
+ # Skip empty input
254
+ if not input_str:
255
+ continue
206
256
 
207
- # Handle quitting
208
- if input_str == 'q':
257
+ result = self._process_input_command(stdscr, input_str)
258
+ # None means quit, empty list means continue, otherwise return the result
259
+ if result is None:
209
260
  return None
210
-
211
- # Handle clipboard input
212
- if input_str == 'v':
213
- clipboard_numbers = self.process_clipboard_input(stdscr)
214
- if clipboard_numbers:
215
- return clipboard_numbers
216
-
217
- # Handle individual number
218
- validated_input = self.validate_number_input(input_str)
219
- if validated_input:
220
- return validated_input
221
-
222
- stdscr.addstr(6, 0, f"Invalid input. Please enter a number between 1-{self.word_count}")
223
- stdscr.refresh()
224
- time.sleep(1)
261
+ if result: # Non-empty list
262
+ return result
225
263
 
226
264
  except UnicodeDecodeError as e:
227
- logger.error("Unicode decode error: %s", str(e))
228
- stdscr.addstr(6, 0, "Invalid character input")
229
- stdscr.refresh()
230
- time.sleep(1)
265
+ self._display_error(stdscr, "Invalid character input", e)
231
266
  except ValueError as e:
232
- logger.error("Value error: %s", str(e))
233
- stdscr.addstr(6, 0, "Invalid input format")
234
- stdscr.refresh()
235
- time.sleep(1)
267
+ self._display_error(stdscr, "Invalid input format", e)
236
268
  except Exception as e:
237
- logger.error("Unexpected input error: %s", str(e))
238
- stdscr.addstr(6, 0, "Error processing input")
239
- stdscr.refresh()
240
- time.sleep(1)
269
+ self._display_error(stdscr, "Error processing input", e)
241
270
  finally:
242
271
  curses.noecho()
@@ -45,6 +45,8 @@ class SecureWordInterface:
45
45
  self.input_handler = InputHandler(len(self.words), self.ui_manager)
46
46
  self.display_handler = DisplayHandler(self.words, self.ui_manager)
47
47
  self.state_handler = StateHandler()
48
+ # Set state_handler reference in display_handler
49
+ self.display_handler.state_handler = self.state_handler
48
50
 
49
51
  # For backward compatibility with tests
50
52
  self._initialize_curses = self.ui_manager.initialize
@@ -179,8 +181,10 @@ class SecureWordInterface:
179
181
 
180
182
  return should_reinit, new_positions, new_scroll
181
183
 
182
- def _process_user_input(self, stdscr: Any, positions: List[int], scroll_position: int,
183
- visible_count: int, current_time: float) -> Tuple[bool, int]:
184
+ def _process_user_input(
185
+ self, stdscr: Any, positions: List[int], scroll_position: int,
186
+ visible_count: int, current_time: float
187
+ ) -> Tuple[bool, int]:
184
188
  """
185
189
  Process user input and update state accordingly.
186
190
 
@@ -206,6 +210,8 @@ class SecureWordInterface:
206
210
  # Handle timeout adjustment for input mode
207
211
  if should_reinit:
208
212
  stdscr.timeout(10000)
213
+ # Clear positions to force entering input mode on next loop
214
+ positions.clear()
209
215
 
210
216
  # Handle quit command
211
217
  if should_quit:
@@ -309,9 +315,10 @@ class SecureWordInterface:
309
315
  except Exception as e:
310
316
  logger.debug("Error handling mouse event: %s", str(e))
311
317
 
312
- def _handle_user_input(self, c: int, positions: List[int], scroll_position: int,
313
- visible_count: int, current_time: float
314
- ) -> Tuple[bool, bool, int, List[int]]:
318
+ def _handle_user_input(
319
+ self, c: int, positions: List[int], scroll_position: int,
320
+ visible_count: int, current_time: float
321
+ ) -> Tuple[bool, bool, int, List[int]]:
315
322
  """
316
323
  Process a single user input and determine actions.
317
324
 
@@ -353,11 +360,11 @@ class SecureWordInterface:
353
360
  def _get_load_positions_func(self) -> Callable[[str], Optional[List[int]]]:
354
361
  """Return a function that loads positions from a file."""
355
362
  return self.input_handler.load_positions_from_file
356
-
363
+
357
364
  def _get_handle_autoscroll_func(self) -> Callable[[Optional[int], int, int], int]:
358
365
  """Return a function that handles autoscrolling."""
359
366
  return self.display_handler.handle_autoscroll
360
-
367
+
361
368
  def _load_positions_file(self, file_path: str) -> List[int]:
362
369
  """
363
370
  Load word positions from a file.
@@ -138,7 +138,9 @@ class StateHandler:
138
138
  if positions:
139
139
  self.current_index = 0
140
140
 
141
- def handle_commands(self, key: int, positions: List[int], current_time: float) -> Optional[List[int]]:
141
+ def handle_commands(
142
+ self, key: int, positions: List[int], current_time: float
143
+ ) -> Optional[List[int]]:
142
144
  """
143
145
  Process user commands and update state accordingly.
144
146
 
@@ -23,7 +23,7 @@ class PreBuild(_build_py):
23
23
 
24
24
  setup(
25
25
  name="seedshield",
26
- version="0.2.1", # This should match VERSION in seedshield/config.py
26
+ version="0.2.2", # This should match VERSION in seedshield/config.py
27
27
 
28
28
  packages=find_namespace_packages(
29
29
  include=['seedshield', 'seedshield.*']
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes