|
25 | 25 | from ..capture.convert import bgra_to_rgb_pil |
26 | 26 | from ..config import Config |
27 | 27 | from ..overlay import BannerOverlay, InplaceOverlay |
| 28 | +from ..permissions import ( |
| 29 | + check_accessibility, |
| 30 | + check_screen_recording, |
| 31 | + is_macos, |
| 32 | + open_accessibility_settings, |
| 33 | + open_screen_recording_settings, |
| 34 | + request_accessibility, |
| 35 | + request_screen_recording, |
| 36 | +) |
28 | 37 | from . import keyboard |
29 | 38 | from .workers import ProcessWorker |
30 | 39 |
|
@@ -140,6 +149,10 @@ def _setup_ui(self): |
140 | 149 |
|
141 | 150 | layout.addWidget(models_group) |
142 | 151 |
|
| 152 | + # macOS Permissions Section (only shown on macOS) |
| 153 | + if is_macos(): |
| 154 | + self._setup_permissions_ui(layout) |
| 155 | + |
143 | 156 | # Window Selection |
144 | 157 | window_group = QGroupBox("Window Selection") |
145 | 158 | window_layout = QHBoxLayout(window_group) |
@@ -301,6 +314,82 @@ def _setup_ui(self): |
301 | 314 | # Status bar |
302 | 315 | self.statusBar().showMessage("Idle") |
303 | 316 |
|
| 317 | + def _setup_permissions_ui(self, layout: QVBoxLayout): |
| 318 | + """Set up macOS permissions section.""" |
| 319 | + permissions_group = QGroupBox("macOS Permissions") |
| 320 | + permissions_layout = QGridLayout(permissions_group) |
| 321 | + |
| 322 | + # Screen Recording row |
| 323 | + permissions_layout.addWidget(QLabel("Screen Recording"), 0, 0) |
| 324 | + self._screen_recording_status = QLabel() |
| 325 | + permissions_layout.addWidget(self._screen_recording_status, 0, 1) |
| 326 | + self._screen_recording_btn = QPushButton("Grant") |
| 327 | + self._screen_recording_btn.setFixedWidth(80) |
| 328 | + self._screen_recording_btn.clicked.connect(self._on_request_screen_recording) |
| 329 | + permissions_layout.addWidget(self._screen_recording_btn, 0, 2) |
| 330 | + |
| 331 | + # Accessibility row (required for global hotkeys) |
| 332 | + permissions_layout.addWidget(QLabel("Accessibility"), 1, 0) |
| 333 | + self._accessibility_status = QLabel() |
| 334 | + permissions_layout.addWidget(self._accessibility_status, 1, 1) |
| 335 | + self._accessibility_btn = QPushButton("Grant") |
| 336 | + self._accessibility_btn.setFixedWidth(80) |
| 337 | + self._accessibility_btn.clicked.connect(self._on_request_accessibility) |
| 338 | + permissions_layout.addWidget(self._accessibility_btn, 1, 2) |
| 339 | + |
| 340 | + # Set column stretch |
| 341 | + permissions_layout.setColumnStretch(0, 1) |
| 342 | + |
| 343 | + layout.addWidget(permissions_group) |
| 344 | + |
| 345 | + # Initial permission check |
| 346 | + self._update_permissions_status() |
| 347 | + |
| 348 | + def _update_permissions_status(self): |
| 349 | + """Update the permission status indicators.""" |
| 350 | + if not is_macos(): |
| 351 | + return |
| 352 | + |
| 353 | + # Screen Recording |
| 354 | + if check_screen_recording(): |
| 355 | + self._screen_recording_status.setText("✓ Granted") |
| 356 | + self._screen_recording_status.setStyleSheet("color: green;") |
| 357 | + self._screen_recording_btn.setVisible(False) |
| 358 | + else: |
| 359 | + self._screen_recording_status.setText("✗ Required") |
| 360 | + self._screen_recording_status.setStyleSheet("color: red;") |
| 361 | + self._screen_recording_btn.setVisible(True) |
| 362 | + |
| 363 | + # Accessibility |
| 364 | + if check_accessibility(): |
| 365 | + self._accessibility_status.setText("✓ Granted") |
| 366 | + self._accessibility_status.setStyleSheet("color: green;") |
| 367 | + self._accessibility_btn.setVisible(False) |
| 368 | + else: |
| 369 | + self._accessibility_status.setText("✗ Required") |
| 370 | + self._accessibility_status.setStyleSheet("color: red;") |
| 371 | + self._accessibility_btn.setVisible(True) |
| 372 | + |
| 373 | + def _on_request_screen_recording(self): |
| 374 | + """Handle Screen Recording grant button click.""" |
| 375 | + # Try to request permission (triggers system dialog if first time) |
| 376 | + if not request_screen_recording(): |
| 377 | + # Already denied, open System Settings |
| 378 | + open_screen_recording_settings() |
| 379 | + |
| 380 | + # Update status after a short delay (permission may take a moment to register) |
| 381 | + QTimer.singleShot(500, self._update_permissions_status) |
| 382 | + |
| 383 | + def _on_request_accessibility(self): |
| 384 | + """Handle Accessibility grant button click.""" |
| 385 | + # Try to request permission (triggers system dialog) |
| 386 | + if not request_accessibility(): |
| 387 | + # Already denied, open System Settings |
| 388 | + open_accessibility_settings() |
| 389 | + |
| 390 | + # Update status after a short delay |
| 391 | + QTimer.singleShot(500, self._update_permissions_status) |
| 392 | + |
304 | 393 | def _load_models(self): |
305 | 394 | """Start worker thread and load OCR/translation models.""" |
306 | 395 | self.statusBar().showMessage("Loading models...") |
@@ -357,10 +446,7 @@ def _update_status_label(self, label: QLabel, status: str): |
357 | 446 |
|
358 | 447 | def _update_fix_button_visibility(self): |
359 | 448 | """Show/hide the Fix Models button based on model status.""" |
360 | | - has_error = ( |
361 | | - self._ocr_status_label.text() == "Error" |
362 | | - or self._translation_status_label.text() == "Error" |
363 | | - ) |
| 449 | + has_error = self._ocr_status_label.text() == "Error" or self._translation_status_label.text() == "Error" |
364 | 450 | self._fix_models_btn.setVisible(has_error) |
365 | 451 |
|
366 | 452 | def _on_fix_models(self): |
|
0 commit comments