|
37 | 37 | "no", |
38 | 38 | "off", |
39 | 39 | } |
| 40 | +DISMISS_SMALL_GATEWAY_DIALOGS = os.environ.get( |
| 41 | + "IBKR_DISMISS_SMALL_GATEWAY_DIALOGS", |
| 42 | + "yes", |
| 43 | +).strip().lower() not in { |
| 44 | + "0", |
| 45 | + "false", |
| 46 | + "no", |
| 47 | + "off", |
| 48 | +} |
40 | 49 |
|
41 | 50 | # Window titles to search for 2FA prompts. Live IBKR accounts can show mobile |
42 | 51 | # push / IB Key wording instead of the shorter TOTP-oriented prompts. |
|
71 | 80 | ) |
72 | 81 | DISMISSIBLE_DIALOG_SEARCH_PATTERNS = [ |
73 | 82 | "Login Messages", |
| 83 | + "IBKR Gateway", |
74 | 84 | ] |
75 | 85 | DISMISSIBLE_DIALOG_TITLE_KEYWORDS = ( |
76 | 86 | "login messages", |
77 | 87 | ) |
| 88 | +SMALL_GATEWAY_DIALOG_TITLE = "ibkr gateway" |
| 89 | +SMALL_GATEWAY_DIALOG_MAX_WIDTH = 650 |
| 90 | +SMALL_GATEWAY_DIALOG_MAX_HEIGHT = 220 |
78 | 91 | # Current IBKR Gateway TOTP prompts place the code field in the upper half of |
79 | 92 | # the compact dialog. Keep the click centered on the text field instead of the |
80 | 93 | # button/link area below it. |
@@ -205,9 +218,21 @@ def is_auth_candidate(title): |
205 | 218 | return any(keyword in normalized_title for keyword in AUTH_TITLE_KEYWORDS) |
206 | 219 |
|
207 | 220 |
|
208 | | -def is_dismissible_dialog_candidate(title): |
| 221 | +def is_small_gateway_dialog(title, width, height): |
| 222 | + if not DISMISS_SMALL_GATEWAY_DIALOGS: |
| 223 | + return False |
| 224 | + if title.lower() != SMALL_GATEWAY_DIALOG_TITLE: |
| 225 | + return False |
| 226 | + if not width or not height: |
| 227 | + return False |
| 228 | + return width <= SMALL_GATEWAY_DIALOG_MAX_WIDTH and height <= SMALL_GATEWAY_DIALOG_MAX_HEIGHT |
| 229 | + |
| 230 | + |
| 231 | +def is_dismissible_dialog_candidate(title, width=None, height=None): |
209 | 232 | normalized_title = title.lower() |
210 | | - return any(keyword in normalized_title for keyword in DISMISSIBLE_DIALOG_TITLE_KEYWORDS) |
| 233 | + if any(keyword in normalized_title for keyword in DISMISSIBLE_DIALOG_TITLE_KEYWORDS): |
| 234 | + return True |
| 235 | + return is_small_gateway_dialog(title, width, height) |
211 | 236 |
|
212 | 237 |
|
213 | 238 | def find_windows_by_patterns(patterns): |
@@ -243,9 +268,9 @@ def find_dismissible_dialogs(): |
243 | 268 | candidates = [] |
244 | 269 | for window_id in reversed(find_windows_by_patterns(DISMISSIBLE_DIALOG_SEARCH_PATTERNS)): |
245 | 270 | title = get_window_title(window_id) |
246 | | - if not is_dismissible_dialog_candidate(title): |
247 | | - continue |
248 | 271 | width, height = get_window_geometry(window_id) |
| 272 | + if not is_dismissible_dialog_candidate(title, width, height): |
| 273 | + continue |
249 | 274 | candidates.append(WindowCandidate(window_id, title, width, height)) |
250 | 275 | return candidates |
251 | 276 |
|
|
0 commit comments