1.0.1
This commit is contained in:
@@ -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'),
|
||||
)
|
||||
|
||||
|
||||
24
build_app.sh
24
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 ""
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
29
entitlements.plist
Normal file
29
entitlements.plist
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- Allow the app to control other applications -->
|
||||
<key>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
|
||||
<!-- Allow accessibility API access -->
|
||||
<key>com.apple.security.personal-information.accessibility</key>
|
||||
<true/>
|
||||
|
||||
<!-- Allow input monitoring (for PyAutoGUI) -->
|
||||
<key>com.apple.security.device.microphone</key>
|
||||
<false/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<false/>
|
||||
|
||||
<!-- Allow network access if needed -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
<!-- Allow file system access -->
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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"
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user