diff --git a/HereIAm.spec b/HereIAm.spec index b73b767..cbb0c02 100644 --- a/HereIAm.spec +++ b/HereIAm.spec @@ -84,8 +84,8 @@ exe = EXE( disable_windowed_traceback=False, argv_emulation=False, target_arch=None, - codesign_identity=None, - entitlements_file=None, + codesign_identity=None, # Will be signed manually after build + entitlements_file='entitlements.plist', # Add entitlements for accessibility icon=str(assets_dir / 'Enabled.icns'), ) diff --git a/build_app.sh b/build_app.sh index dcc7640..4bce903 100755 --- a/build_app.sh +++ b/build_app.sh @@ -97,15 +97,35 @@ if [ -d "dist/HereIAm.app" ]; then echo -e "${BLUE}๐Ÿงช Testing app launch (will quit immediately)...${NC}" timeout 5s dist/HereIAm.app/Contents/MacOS/HereIAm || true + # Try to self-sign the app for better permissions + echo -e "${BLUE}๐Ÿ” Attempting to self-sign app for better permissions...${NC}" + if command -v codesign >/dev/null 2>&1; then + # Create a temporary signing identity if none exists + codesign --force --deep --sign - dist/HereIAm.app || { + echo -e "${YELLOW}โš ๏ธ Code signing failed - app may have permission issues${NC}" + } + + # Verify the signing + if codesign --verify --deep --strict --verbose=2 dist/HereIAm.app >/dev/null 2>&1; then + echo -e "${GREEN}โœ… App successfully self-signed${NC}" + else + echo -e "${YELLOW}โš ๏ธ App signing verification failed${NC}" + fi + else + echo -e "${YELLOW}โš ๏ธ codesign not available - app may have permission issues${NC}" + fi + echo "" echo -e "${GREEN}๐ŸŽ‰ Build completed successfully!${NC}" echo -e "${GREEN}๐Ÿ“ฑ Your app is ready at: dist/HereIAm.app${NC}" echo "" echo -e "${YELLOW}๐Ÿ“ Next steps:${NC}" echo "1. Test the app: open dist/HereIAm.app" - echo "2. For distribution, consider code signing:" + echo "2. Grant Accessibility permissions in System Preferences > Privacy & Security > Accessibility" + echo "3. If mouse movement still doesn't work, check Console.app for error messages" + echo "4. For distribution, consider proper code signing:" echo " codesign --deep --force --verify --verbose --sign \"Developer ID Application: Your Name\" dist/HereIAm.app" - echo "3. Create a DMG for distribution:" + echo "5. Create a DMG for distribution:" echo " ./create_dmg.sh" echo "" diff --git a/create_dmg.sh b/create_dmg.sh index b810b19..4d82440 100755 --- a/create_dmg.sh +++ b/create_dmg.sh @@ -21,7 +21,7 @@ cd "$SCRIPT_DIR" # Configuration APP_NAME="HereIAm" -VERSION="1.0.0" +VERSION="1.0.1" DMG_NAME="${APP_NAME}-${VERSION}" APP_PATH="dist/${APP_NAME}.app" DMG_DIR="dmg_temp" diff --git a/entitlements.plist b/entitlements.plist new file mode 100644 index 0000000..dd335ab --- /dev/null +++ b/entitlements.plist @@ -0,0 +1,29 @@ + + + + + + com.apple.security.automation.apple-events + + + + com.apple.security.personal-information.accessibility + + + + com.apple.security.device.microphone + + com.apple.security.device.camera + + + + com.apple.security.network.client + + + + com.apple.security.files.user-selected.read-write + + com.apple.security.files.downloads.read-write + + + diff --git a/src/__init__.py b/src/__init__.py index 5798c20..7553f6e 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -4,7 +4,7 @@ A sophisticated application that prevents system sleep by intelligently monitori and managing mouse activity. """ -__version__ = "1.0.0" +__version__ = "1.0.1" __author__ = "Jerico Thomas" __email__ = "jerico@tekop.net" __app_name__ = "HereIAm" diff --git a/src/mouse_mover.py b/src/mouse_mover.py index 02d7894..c025e39 100644 --- a/src/mouse_mover.py +++ b/src/mouse_mover.py @@ -12,20 +12,69 @@ class MouseMover: self.move_px = move_px self.countdown = self.wait_time self.mouse_movement_enabled = False - self.xold = 0 - self.yold = 0 + self.xold = None # Start with None to force initial update + self.yold = None self.thread = None self.running = False + self.last_activity_time = time.time() self.logger.debug(f"MouseMover initialized with wait_time={self.wait_time}, move_px={self.move_px}") def _move_mouse(self): """Move mouse slightly and return to original position""" try: - pyautogui.moveRel(0, self.move_px, duration=0.5) # Faster movement - pyautogui.moveRel(0, -self.move_px, duration=0.5) - self.logger.debug(f"Mouse moved {self.move_px}px vertically and back") + # Check if PyAutoGUI is working first + current_pos = pyautogui.position() + self.logger.debug(f"Current mouse position before move: {current_pos}") + + # Use larger movement that's more likely to prevent sleep + move_distance = max(self.move_px, 10) # Ensure at least 10px movement + + # Move in a more noticeable pattern + pyautogui.moveRel(0, move_distance, duration=1) + time.sleep(0.1) # Small pause + pyautogui.moveRel(0, -move_distance, duration=1) + + # Optional: Add a tiny wiggle for good measure + pyautogui.moveRel(1, 0, duration=0.1) + pyautogui.moveRel(-1, 0, duration=0.1) + + # Verify movement worked + new_pos = pyautogui.position() + self.logger.debug(f"Mouse position after move: {new_pos}") + + # Every few movements, also press a "safe" key that won't interfere + # F15 is a function key that typically doesn't do anything + try: + pyautogui.press('f15') + self.logger.debug("Sent F15 key press for additional wake signal") + except Exception as key_error: + self.logger.warning(f"Could not send key press (this may be normal in packaged app): {key_error}") + + self.logger.debug(f"Mouse moved {move_distance}px vertically and back with wiggle") except Exception as e: - self.logger.error(f"Failed to move mouse: {e}") + self.logger.error(f"Failed to move mouse - this may indicate accessibility permission issues: {e}") + # Try alternative approach if PyAutoGUI fails + try: + import subprocess + # Use AppleScript as fallback for mouse movement + script = f""" + tell application "System Events" + set currentPos to (get position of mouse) + set x to item 1 of currentPos + set y to item 2 of currentPos + set mouse position {{x, y + {max(self.move_px, 10)}}} + delay 0.1 + set mouse position {{x, y}} + end tell + """ + result = subprocess.run(['osascript', '-e', script], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + self.logger.info("Successfully moved mouse using AppleScript fallback") + else: + self.logger.error(f"AppleScript fallback failed: {result.stderr}") + except Exception as fallback_error: + self.logger.error(f"Both PyAutoGUI and AppleScript fallback failed: {fallback_error}") + self.logger.error("This app may need to be granted Accessibility permissions in System Preferences") def start(self): """Start the mouse movement monitoring in a separate thread""" @@ -56,29 +105,56 @@ class MouseMover: """Main monitoring loop that runs in separate thread""" self.logger.debug("Mouse monitoring thread started") + # Initialize with 0,0 like original script + self.xold, self.yold = 0, 0 + self.last_activity_time = time.time() + self.logger.debug(f"Initial mouse position tracking: ({self.xold}, {self.yold})") + while self.running and self.mouse_movement_enabled: try: - x, y = pyautogui.position() + # Try to get mouse position with error handling + try: + x, y = pyautogui.position() + except Exception as pos_error: + self.logger.error(f"Failed to get mouse position: {pos_error}") + self.logger.error("This indicates PyAutoGUI accessibility issues in packaged app") + # Sleep and continue to avoid tight error loop + time.sleep(5) + continue - # Check if timer has expired + current_time = time.time() + + # Check if timer has expired first (like original script) if self.countdown <= 0: self._move_mouse() self.logger.info(f"Timer expired ({self.wait_time}s), mouse moved automatically") self.countdown = self.wait_time + self.last_activity_time = current_time + # After our movement, continue to next iteration immediately continue - # Check if mouse has moved - if x == self.xold and y == self.yold: - # Mouse hasn't moved, count down - self.logger.debug(f"Mouse idle, countdown: {self.countdown}s") + # Use the original script's exact logic: if (x == xold or y == yold) + if x == self.xold or y == self.yold: + # Mouse is considered idle - count down by 1 and sleep 1 second (like original) + self.logger.debug(f"Mouse idle (x={x} vs xold={self.xold}, y={y} vs yold={self.yold}), countdown: {self.countdown}s") + + # Sleep for 1 second and decrement by 1 (exactly like original script) time.sleep(1) self.countdown -= 1 + self.logger.debug(f"Countdown: {self.countdown}") + else: - # Mouse moved, reset timer - self.logger.debug(f"Mouse moved to ({x}, {y}), timer reset") + # Mouse moved to a new position, reset timer and update yold (exactly like original) + self.logger.info(f"Mouse moved to ({x}, {y}) from ({self.xold}, {self.yold}), timer reset") self.countdown = self.wait_time - self.xold = x + self.last_activity_time = current_time + # Only update yold, keep xold at 0 (matches original script behavior exactly) self.yold = y + # Note: intentionally NOT updating self.xold to match original behavior + + # Check if we should stop (more frequently than the 10-second chunks) + if not self.running or not self.mouse_movement_enabled: + break except Exception as e: self.logger.error(f"Error in mouse monitoring: {e}") diff --git a/src/options_dialog.py b/src/options_dialog.py index b96cec6..0bdfd0f 100644 --- a/src/options_dialog.py +++ b/src/options_dialog.py @@ -1,6 +1,8 @@ from PyQt5 import QtWidgets, QtCore, QtGui import logging -from logging_config import get_logger +import subprocess +import os +from logging_config import get_logger, get_log_directory_path from launch_agent import get_launch_agent_manager @@ -28,7 +30,7 @@ class OptionsDialog(QtWidgets.QDialog): """Setup the dialog UI""" self.setWindowTitle("HereIAm Options") self.setModal(True) - self.setFixedSize(400, 220) # Increased height to accommodate new option + self.setFixedSize(400, 260) # Increased height to accommodate new button # Ensure dialog appears on top and is properly managed self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowStaysOnTopHint) @@ -81,6 +83,12 @@ class OptionsDialog(QtWidgets.QDialog): layout.addLayout(form_layout) + # Open Logs button + logs_button = QtWidgets.QPushButton("Open Logs Directory") + logs_button.setToolTip("Open the directory containing HereIAm log files") + logs_button.clicked.connect(self.open_logs_directory) + layout.addWidget(logs_button) + # Button box button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Save | QtWidgets.QDialogButtonBox.Cancel @@ -144,6 +152,41 @@ class OptionsDialog(QtWidgets.QDialog): self.logger.error(f"Error getting dialog values: {e}") return None + def open_logs_directory(self): + """Open the logs directory in Finder (macOS)""" + try: + log_dir = get_log_directory_path() + self.logger.debug(f"Opening logs directory: {log_dir}") + + # Ensure the directory exists + if not os.path.exists(log_dir): + self.logger.warning(f"Logs directory does not exist: {log_dir}") + QtWidgets.QMessageBox.warning( + self, + "Directory Not Found", + f"The logs directory does not exist yet:\n{log_dir}\n\nLogs will be created when the application runs." + ) + return + + # Open directory in Finder on macOS + subprocess.run(['open', log_dir], check=True) + self.logger.info(f"Successfully opened logs directory: {log_dir}") + + except subprocess.CalledProcessError as e: + self.logger.error(f"Failed to open logs directory: {e}") + QtWidgets.QMessageBox.critical( + self, + "Error Opening Directory", + f"Failed to open logs directory:\n{str(e)}" + ) + except Exception as e: + self.logger.error(f"Unexpected error opening logs directory: {e}") + QtWidgets.QMessageBox.critical( + self, + "Error", + f"An unexpected error occurred:\n{str(e)}" + ) + @staticmethod def get_options(current_wait_time=240, current_start_enabled=True, current_log_level="INFO", current_move_px=10, current_launch_at_startup=False, parent=None): """Static method to show the dialog and return values""" diff --git a/test_startup.py b/test_startup.py deleted file mode 100755 index aa6aeeb..0000000 --- a/test_startup.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for Launch Agent functionality. -""" - -import sys -import os - -# Add src directory to path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) - -from launch_agent import get_launch_agent_manager -from logging_config import setup_logging, get_logger - -def test_launch_agent(): - """Test the launch agent functionality""" - setup_logging(debug=True) - logger = get_logger(__name__) - - print("๐Ÿงช Testing Launch Agent Functionality") - print("=" * 40) - - # Get launch agent manager - manager = get_launch_agent_manager() - - # Check current status - is_enabled = manager.is_startup_enabled() - print(f"๐Ÿ“‹ Current startup status: {'Enabled' if is_enabled else 'Disabled'}") - - if is_enabled: - info = manager.get_launch_agent_info() - if info: - print(f"๐Ÿ“‚ Plist path: {info['path']}") - print(f"๐Ÿš€ Program: {' '.join(info['program_arguments'])}") - - # Test app path detection - app_path = manager.get_app_path() - print(f"๐Ÿ“ฑ Detected app path: {app_path}") - - # Test plist creation (don't install it) - plist_data = manager.create_launch_agent_plist() - print(f"๐Ÿ“„ Generated plist data:") - for key, value in plist_data.items(): - print(f" {key}: {value}") - - print("\nโœ… Launch Agent test completed!") - print("\nTo test startup functionality:") - print("1. Build the app: ./build_app.sh") - print("2. Run the app and go to Options") - print("3. Enable 'Launch at Startup'") - print("4. Log out and back in to test") - -if __name__ == "__main__": - test_launch_agent()