99import subprocess
1010import json
1111import tempfile
12+ import threading
13+ import time
1214from pathlib import Path
1315from typing import Optional , Callable
1416from datetime import datetime
@@ -64,23 +66,54 @@ def gui_main() -> None:
6466 class PlaceholderEntry (ttk .Entry ):
6567 """Entry widget with placeholder text support."""
6668 def __init__ (self , parent , placeholder = "" , ** kwargs ):
69+ # Extract textvariable if provided
70+ self ._string_var = kwargs .pop ('textvariable' , None )
6771 super ().__init__ (parent , ** kwargs )
6872 self .placeholder = placeholder
6973 self .placeholder_color = "#999999"
70- self .default_color = "SystemWindowText"
74+ # Use platform-appropriate default text color
75+ if sys .platform == "win32" :
76+ self .default_color = "SystemWindowText"
77+ else :
78+ self .default_color = "black"
7179 self ._has_placeholder = False
80+ self ._ignore_trace = False
81+
82+ # Set up trace on the variable if provided
83+ if self ._string_var :
84+ self ._string_var .trace_add ("write" , self ._on_var_changed )
7285
7386 if placeholder :
7487 self ._show_placeholder ()
7588
7689 self .bind ("<FocusIn>" , self ._on_focus_in )
7790 self .bind ("<FocusOut>" , self ._on_focus_out )
91+ self .bind ("<KeyRelease>" , self ._on_key_release )
92+
93+ def _on_var_changed (self , * args ):
94+ """Handle external changes to the StringVar."""
95+ if not self ._ignore_trace and self ._string_var :
96+ value = self ._string_var .get ()
97+ if not value and not self ._has_placeholder :
98+ self ._show_placeholder ()
99+
100+ def _on_key_release (self , * args ):
101+ """Update StringVar when user types (without placeholder)."""
102+ if not self ._has_placeholder and self ._string_var :
103+ self ._ignore_trace = True
104+ self ._string_var .set (self .get ())
105+ self ._ignore_trace = False
78106
79107 def _show_placeholder (self , * args ):
80108 if not self .get ():
81109 self ._has_placeholder = True
82110 self .insert (0 , self .placeholder )
83111 self .config (foreground = self .placeholder_color )
112+ # Don't set placeholder in the StringVar
113+ if self ._string_var :
114+ self ._ignore_trace = True
115+ self ._string_var .set ("" )
116+ self ._ignore_trace = False
84117
85118 def _on_focus_in (self , * args ):
86119 if self ._has_placeholder :
@@ -89,10 +122,20 @@ def _on_focus_in(self, *args):
89122 self ._has_placeholder = False
90123
91124 def _on_focus_out (self , * args ):
125+ # Update StringVar with actual value
126+ if not self ._has_placeholder and self ._string_var :
127+ self ._ignore_trace = True
128+ self ._string_var .set (self .get ())
129+ self ._ignore_trace = False
130+
92131 if not self .get ():
93132 self ._show_placeholder ()
94133
95134 def get_value (self ):
135+ """Get the actual value, returning empty string if showing placeholder."""
136+ if self ._has_placeholder :
137+ return ""
138+ return self .get ()
96139 """Get actual value without placeholder."""
97140 return "" if self ._has_placeholder else self .get ()
98141
@@ -1538,7 +1581,7 @@ def load_template():
15381581 qr_box ,
15391582 text = "Generate QR" ,
15401583 command = lambda : self ._run_command (
1541- ["python" , str (Path (__file__ ).parent / "app.py" ), "qr-generate" ,
1584+ ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "qr-generate" ,
15421585 "--text" , qr_text .get (), "--out" , qr_out .get ()],
15431586 "QR generate" ,
15441587 ),
@@ -1569,7 +1612,7 @@ def load_template():
15691612 ttk .Entry (portable_box , textvariable = port_frame ).pack (fill = tk .X , padx = 10 , pady = (0 , 8 ))
15701613
15711614 def run_portable ():
1572- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "portable-decoder" ,
1615+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "portable-decoder" ,
15731616 "--input" , port_in .get (), "--out" , port_out .get ()]
15741617 if port_method .get ():
15751618 cmd .extend (["--method" , port_method .get ()])
@@ -1591,7 +1634,7 @@ def run_portable():
15911634 assoc_box ,
15921635 text = "Generate .reg" ,
15931636 command = lambda : self ._run_command (
1594- ["python" , str (Path (__file__ ).parent / "app.py" ), "file-assoc" , "--out" , assoc_out .get ()],
1637+ ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "file-assoc" , "--out" , assoc_out .get ()],
15951638 "File association" ,
15961639 ),
15971640 ).pack (anchor = tk .W , padx = 10 , pady = (0 , 10 ))
@@ -2128,7 +2171,7 @@ def task():
21282171 self .root .after (0 , self .status .stop )
21292172 self .root .after (0 , lambda : self ._set_status (f"{ action } error" ))
21302173 self .root .after (0 , lambda : self ._add_history (action , "Error" , str (exc )))
2131- self .root .after (0 , lambda : messagebox .showerror ("Error" , str (exc )))
2174+ self .root .after (0 , lambda e = exc : messagebox .showerror ("Error" , str (e )))
21322175
21332176 threading .Thread (target = task , daemon = True ).start ()
21342177
@@ -2165,7 +2208,7 @@ def _encode_image(self, cover: str, message: str, payload_file: str, password: s
21652208 return
21662209
21672210 def build_cmd (input_img : str , out_img : str ) -> list [str ]:
2168- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "encode" ,
2211+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "encode" ,
21692212 "--image" , input_img , "--out" , out_img , "--method" , method ]
21702213 if payload_file :
21712214 cmd .extend (["--in-file" , payload_file ])
@@ -2238,7 +2281,7 @@ def run_nested():
22382281 self .root .after (0 , self .status .stop )
22392282 self .root .after (0 , lambda : self ._set_status ("Image encode failed" ))
22402283 self .root .after (0 , lambda : self ._add_history ("Image encode" , "Failed" , str (exc )))
2241- self .root .after (0 , lambda : messagebox .showerror ("Error" , str (exc )))
2284+ self .root .after (0 , lambda e = exc : messagebox .showerror ("Error" , str (e )))
22422285
22432286 self .root .after (0 , lambda : self ._set_status ("Image encode in progress" ))
22442287 self .root .after (0 , self .status .start )
@@ -2252,7 +2295,7 @@ def _decode_image(self, stego: str, password: str, output: str, method: str, prn
22522295 messagebox .showerror ("Error" , "PRNG key is required for the selected method." )
22532296 return
22542297
2255- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "decode" ,
2298+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "decode" ,
22562299 "--image" , stego , "--method" , method ]
22572300 if password :
22582301 cmd .extend (["--password" , password ])
@@ -2281,7 +2324,7 @@ def _encode_media(self, media_type: str, input_path: str, message: str, payload_
22812324 messagebox .showerror ("Error" , "Select an output file." )
22822325 return
22832326
2284- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), f"{ media_type } -encode" ,
2327+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), f"{ media_type } -encode" ,
22852328 f"--{ media_type } " , input_path , "--out" , output ]
22862329 if payload_file :
22872330 cmd .extend (["--in-file" , payload_file ])
@@ -2306,7 +2349,7 @@ def _decode_media(self, media_type: str, input_path: str, password: str, output:
23062349 messagebox .showerror ("Error" , "Select an output file." )
23072350 return
23082351
2309- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), f"{ media_type } -decode" ,
2352+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), f"{ media_type } -decode" ,
23102353 f"--{ media_type } " , input_path , "--out" , output ]
23112354 if password :
23122355 cmd .extend (["--password" , password ])
@@ -2325,7 +2368,7 @@ def _encode_pdf(self, pdf_path: str, message: str, payload_file: str, password:
23252368 messagebox .showerror ("Error" , "Select an output PDF." )
23262369 return
23272370
2328- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "pdf-encode" ,
2371+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "pdf-encode" ,
23292372 "--pdf" , pdf_path , "--out" , output ]
23302373 if payload_file :
23312374 cmd .extend (["--in-file" , payload_file ])
@@ -2346,7 +2389,7 @@ def _decode_pdf(self, pdf_path: str, password: str, output: str) -> None:
23462389 if not pdf_path :
23472390 messagebox .showerror ("Error" , "Select a PDF file." )
23482391 return
2349- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "pdf-decode" , "--pdf" , pdf_path ]
2392+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "pdf-decode" , "--pdf" , pdf_path ]
23502393 if output :
23512394 cmd .extend (["--out" , output ])
23522395 if password :
@@ -2376,7 +2419,7 @@ def _encode_gif(self, gif_path: str, frame_index: int, message: str, payload_fil
23762419 messagebox .showerror ("Error" , "PRNG key is required for the selected method." )
23772420 return
23782421
2379- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "gif-encode" ,
2422+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "gif-encode" ,
23802423 "--gif" , gif_path , "--out" , output , "--frame" , str (frame_index ), "--method" , method ]
23812424 if payload_file :
23822425 cmd .extend (["--in-file" , payload_file ])
@@ -2404,7 +2447,7 @@ def _decode_gif(self, gif_path: str, frame_index: int, password: str, output: st
24042447 messagebox .showerror ("Error" , "PRNG key is required for the selected method." )
24052448 return
24062449
2407- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "gif-decode" ,
2450+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "gif-decode" ,
24082451 "--gif" , gif_path , "--frame" , str (frame_index ), "--method" , method ]
24092452 if output :
24102453 cmd .extend (["--out" , output ])
@@ -2437,7 +2480,7 @@ def _encode_video_frame(self, video_path: str, frame_index: int, message: str, p
24372480 messagebox .showerror ("Error" , "PRNG key is required for the selected method." )
24382481 return
24392482
2440- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "video-frame-encode" ,
2483+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "video-frame-encode" ,
24412484 "--video" , video_path , "--out" , output , "--frame" , str (frame_index ), "--method" , method ]
24422485 if payload_file :
24432486 cmd .extend (["--in-file" , payload_file ])
@@ -2465,7 +2508,7 @@ def _decode_video_frame(self, video_path: str, frame_index: int, password: str,
24652508 messagebox .showerror ("Error" , "PRNG key is required for the selected method." )
24662509 return
24672510
2468- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "video-frame-decode" ,
2511+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "video-frame-decode" ,
24692512 "--video" , video_path , "--frame" , str (frame_index ), "--method" , method ]
24702513 if output :
24712514 cmd .extend (["--out" , output ])
@@ -2502,7 +2545,7 @@ def task():
25022545 self .root .after (0 , lambda : self ._set_status ("Batch encode in progress" ))
25032546 for idx , path in enumerate (files , start = 1 ):
25042547 out_path = Path (output_dir ) / f"{ Path (path ).stem } _stego.png"
2505- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "encode" ,
2548+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "encode" ,
25062549 "--image" , path , "--out" , str (out_path ), "--message" , message ,
25072550 "--method" , method ]
25082551 if password :
@@ -2543,7 +2586,7 @@ def task():
25432586 self .root .after (0 , lambda : self ._set_status ("Batch decode in progress" ))
25442587 for idx , path in enumerate (files , start = 1 ):
25452588 out_path = Path (output_dir ) / f"{ Path (path ).stem } _decoded.bin"
2546- cmd = ["python" , str (Path (__file__ ).parent / "app.py" ), "decode" ,
2589+ cmd = ["python" , str (Path (__file__ ).parent . parent / "app.py" ), "decode" ,
25472590 "--image" , path , "--out" , str (out_path ), "--method" , method ]
25482591 if password :
25492592 cmd .extend (["--password" , password ])
0 commit comments