computer-use-ootb-internal 0.0.110__py3-none-any.whl → 0.0.112__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- computer_use_ootb_internal/app_teachmode.py +44 -61
- computer_use_ootb_internal/guard_service.py +436 -0
- computer_use_ootb_internal/preparation/__init__.py +1 -0
- computer_use_ootb_internal/preparation/star_rail_prepare.py +65 -0
- computer_use_ootb_internal/service_manager.py +142 -0
- {computer_use_ootb_internal-0.0.110.dist-info → computer_use_ootb_internal-0.0.112.dist-info}/METADATA +5 -5
- {computer_use_ootb_internal-0.0.110.dist-info → computer_use_ootb_internal-0.0.112.dist-info}/RECORD +9 -6
- computer_use_ootb_internal-0.0.112.dist-info/entry_points.txt +4 -0
- computer_use_ootb_internal/startup_utils.py +0 -194
- computer_use_ootb_internal-0.0.110.dist-info/entry_points.txt +0 -4
- {computer_use_ootb_internal-0.0.110.dist-info → computer_use_ootb_internal-0.0.112.dist-info}/WHEEL +0 -0
@@ -8,6 +8,9 @@ import platform # Add platform import
|
|
8
8
|
import pyautogui # Add pyautogui import
|
9
9
|
import webbrowser # Add webbrowser import
|
10
10
|
import os # Import os for path joining
|
11
|
+
import logging # Import logging
|
12
|
+
import importlib # For dynamic imports
|
13
|
+
import pkgutil # To find modules
|
11
14
|
from fastapi import FastAPI, Request
|
12
15
|
from fastapi.responses import JSONResponse
|
13
16
|
from fastapi.middleware.cors import CORSMiddleware
|
@@ -90,67 +93,47 @@ class SharedState:
|
|
90
93
|
shared_state = None
|
91
94
|
rate_limiter = RateLimiter(interval_seconds=2)
|
92
95
|
|
93
|
-
#
|
96
|
+
# Set up logging for this module
|
97
|
+
log = logging.getLogger(__name__)
|
98
|
+
|
94
99
|
def prepare_environment(state):
|
95
|
-
"""
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
# Disable failsafe before clicking
|
133
|
-
pyautogui.FAILSAFE = False
|
134
|
-
print("PyAutoGUI failsafe temporarily disabled.")
|
135
|
-
|
136
|
-
print(f"Clicking at coordinates: ({click_x}, {click_y})")
|
137
|
-
pyautogui.click(click_x, click_y)
|
138
|
-
time.sleep(2)
|
139
|
-
pyautogui.click(click_x, click_y)
|
140
|
-
|
141
|
-
# Re-enable failsafe (optional, as script might end anyway)
|
142
|
-
# pyautogui.FAILSAFE = True
|
143
|
-
# print("PyAutoGUI failsafe re-enabled.")
|
144
|
-
|
145
|
-
except Exception as e:
|
146
|
-
print(f"Error during environment preparation (browser/click): {e}")
|
147
|
-
finally:
|
148
|
-
# Ensure failsafe is re-enabled if an error occurs after disabling it
|
149
|
-
pyautogui.FAILSAFE = True
|
150
|
-
print("PyAutoGUI failsafe re-enabled.")
|
151
|
-
else:
|
152
|
-
# Placeholder for potential preparations on other OS or non-Star Rail modes
|
153
|
-
print("Environment preparation: No specific actions required for this OS/mode.")
|
100
|
+
"""Dynamically loads and runs preparation logic based on software name."""
|
101
|
+
# TODO: Replace hardcoded software name with value from shared_state when available
|
102
|
+
software_name = "star rail"
|
103
|
+
# Normalize the software name to be a valid Python module name
|
104
|
+
# Replace spaces/hyphens with underscores, convert to lowercase
|
105
|
+
module_name_base = software_name.replace(" ", "_").replace("-", "_").lower()
|
106
|
+
module_to_run = f"{module_name_base}_prepare"
|
107
|
+
|
108
|
+
log.info(f"Attempting preparation for software: '{software_name}' (Module: '{module_to_run}')")
|
109
|
+
|
110
|
+
try:
|
111
|
+
# Construct the full module path within the package
|
112
|
+
prep_package = "computer_use_ootb_internal.preparation"
|
113
|
+
full_module_path = f"{prep_package}.{module_to_run}"
|
114
|
+
|
115
|
+
# Dynamically import the module
|
116
|
+
# Check if module exists first using pkgutil to avoid import errors
|
117
|
+
# Note: pkgutil.find_loader might be deprecated, consider importlib.util.find_spec
|
118
|
+
loader = pkgutil.find_loader(full_module_path)
|
119
|
+
if loader is None:
|
120
|
+
log.warning(f"Preparation module '{full_module_path}' not found. Skipping preparation.")
|
121
|
+
return
|
122
|
+
|
123
|
+
prep_module = importlib.import_module(full_module_path)
|
124
|
+
|
125
|
+
# Check if the module has the expected function
|
126
|
+
if hasattr(prep_module, "run_preparation") and callable(prep_module.run_preparation):
|
127
|
+
log.info(f"Running preparation function from {full_module_path}...")
|
128
|
+
prep_module.run_preparation(state)
|
129
|
+
log.info(f"Preparation function from {full_module_path} completed.")
|
130
|
+
else:
|
131
|
+
log.warning(f"Module {full_module_path} found, but does not have a callable 'run_preparation' function. Skipping.")
|
132
|
+
|
133
|
+
except ModuleNotFoundError:
|
134
|
+
log.warning(f"Preparation module '{full_module_path}' not found. Skipping preparation.")
|
135
|
+
except Exception as e:
|
136
|
+
log.error(f"Error during dynamic preparation loading/execution for '{module_to_run}': {e}", exc_info=True)
|
154
137
|
|
155
138
|
|
156
139
|
@app.post("/update_params")
|
@@ -177,7 +160,7 @@ async def update_parameters(request: Request):
|
|
177
160
|
|
178
161
|
log_ootb_request(shared_state.server_url, "update_params", data)
|
179
162
|
|
180
|
-
# Call the preparation function here, after parameters are updated
|
163
|
+
# Call the (now dynamic) preparation function here, after parameters are updated
|
181
164
|
prepare_environment(shared_state)
|
182
165
|
|
183
166
|
return JSONResponse(
|
@@ -0,0 +1,436 @@
|
|
1
|
+
# src/computer_use_ootb_internal/guard_service.py
|
2
|
+
import sys
|
3
|
+
import os
|
4
|
+
import time
|
5
|
+
import logging
|
6
|
+
import subprocess
|
7
|
+
import pathlib
|
8
|
+
import ctypes
|
9
|
+
import requests # For server polling
|
10
|
+
import servicemanager # From pywin32
|
11
|
+
import win32serviceutil # From pywin32
|
12
|
+
import win32service # From pywin32
|
13
|
+
import win32event # From pywin32
|
14
|
+
import win32api # From pywin32
|
15
|
+
import win32process # From pywin32
|
16
|
+
import win32security # From pywin32
|
17
|
+
import win32profile # From pywin32
|
18
|
+
import win32ts # From pywin32 (Terminal Services API)
|
19
|
+
import win32con # Added import
|
20
|
+
import psutil # For process/user info
|
21
|
+
|
22
|
+
# --- Configuration ---
|
23
|
+
# Internal service name
|
24
|
+
_SERVICE_NAME = "OOTBGuardService"
|
25
|
+
# Display name in Windows Services MMC
|
26
|
+
_SERVICE_DISPLAY_NAME = "OOTB Guard Service"
|
27
|
+
# Description in Windows Services MMC
|
28
|
+
_SERVICE_DESCRIPTION = "Background service for OOTB monitoring and remote management."
|
29
|
+
# Package name for updates
|
30
|
+
_PACKAGE_NAME = "computer-use-ootb-internal"
|
31
|
+
# Main module to start/stop for users
|
32
|
+
_OOTB_MODULE = "computer_use_ootb_internal.app_teachmode"
|
33
|
+
# Server endpoint to poll for commands (replace with your actual URL)
|
34
|
+
_SERVER_COMMAND_URL = "http://52.160.105.102:7000/api/guard" # Using HTTP as port was specified
|
35
|
+
# How often to poll the server (in seconds)
|
36
|
+
_POLLING_INTERVAL = 60
|
37
|
+
# Placeholder for a machine identifier or API key for the server
|
38
|
+
_MACHINE_ID = "YOUR_MACHINE_ID_OR_API_KEY" # EXAMPLE - Implement actual ID/auth
|
39
|
+
# Log file location (consider using Windows Event Log instead for production)
|
40
|
+
_LOG_FILE = pathlib.Path(os.environ['PROGRAMDATA']) / "OOTBGuardService" / "guard.log"
|
41
|
+
# --- End Configuration ---
|
42
|
+
|
43
|
+
# Ensure log directory exists
|
44
|
+
_LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
45
|
+
|
46
|
+
logging.basicConfig(
|
47
|
+
filename=_LOG_FILE,
|
48
|
+
level=logging.INFO,
|
49
|
+
format='%(asctime)s %(levelname)s %(message)s'
|
50
|
+
)
|
51
|
+
|
52
|
+
def get_python_executable():
|
53
|
+
"""Gets the quoted path to the current python executable."""
|
54
|
+
python_exe = sys.executable
|
55
|
+
if " " in python_exe and not python_exe.startswith('"'):
|
56
|
+
python_exe = f'"{python_exe}"'
|
57
|
+
return python_exe
|
58
|
+
|
59
|
+
def get_pip_executable():
|
60
|
+
"""Tries to locate the pip executable in the same environment."""
|
61
|
+
python_path = pathlib.Path(sys.executable)
|
62
|
+
pip_path = python_path.parent / "Scripts" / "pip.exe"
|
63
|
+
if pip_path.exists():
|
64
|
+
pip_exe = str(pip_path)
|
65
|
+
if " " in pip_exe and not pip_exe.startswith('"'):
|
66
|
+
pip_exe = f'"{pip_exe}"'
|
67
|
+
return pip_exe
|
68
|
+
else:
|
69
|
+
logging.warning("pip.exe not found in Scripts directory. Falling back to 'python -m pip'.")
|
70
|
+
return f"{get_python_executable()} -m pip"
|
71
|
+
|
72
|
+
def log_info(msg):
|
73
|
+
logging.info(msg)
|
74
|
+
try:
|
75
|
+
servicemanager.LogInfoMsg(str(msg)) # Also log to Windows Event Log Application channel
|
76
|
+
except Exception as e:
|
77
|
+
logging.warning(f"Could not write to Windows Event Log: {e}") # Avoid crashing service if event log fails
|
78
|
+
|
79
|
+
def log_error(msg, exc_info=False):
|
80
|
+
logging.error(msg, exc_info=exc_info)
|
81
|
+
try:
|
82
|
+
servicemanager.LogErrorMsg(str(msg))
|
83
|
+
except Exception as e:
|
84
|
+
logging.warning(f"Could not write error to Windows Event Log: {e}")
|
85
|
+
|
86
|
+
class GuardService(win32serviceutil.ServiceFramework):
|
87
|
+
_svc_name_ = _SERVICE_NAME
|
88
|
+
_svc_display_name_ = _SERVICE_DISPLAY_NAME
|
89
|
+
_svc_description_ = _SERVICE_DESCRIPTION
|
90
|
+
|
91
|
+
def __init__(self, args):
|
92
|
+
win32serviceutil.ServiceFramework.__init__(self, args)
|
93
|
+
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
|
94
|
+
self.is_running = True
|
95
|
+
self.python_exe = get_python_executable()
|
96
|
+
self.pip_command_base = get_pip_executable()
|
97
|
+
self.ootb_command = f"{self.python_exe} -m {_OOTB_MODULE}"
|
98
|
+
self.session = requests.Session() # Reuse session for polling
|
99
|
+
|
100
|
+
def SvcStop(self):
|
101
|
+
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
102
|
+
win32event.SetEvent(self.hWaitStop)
|
103
|
+
self.is_running = False
|
104
|
+
log_info(f"{_SERVICE_NAME} is stopping.")
|
105
|
+
|
106
|
+
def SvcDoRun(self):
|
107
|
+
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
|
108
|
+
servicemanager.PYS_SERVICE_STARTED,
|
109
|
+
(self._svc_name_, ''))
|
110
|
+
self.main_loop()
|
111
|
+
# If main_loop exits cleanly (shouldn't happen with while self.is_running)
|
112
|
+
log_info(f"{_SERVICE_NAME} main loop exited.")
|
113
|
+
|
114
|
+
|
115
|
+
def main_loop(self):
|
116
|
+
log_info(f"{_SERVICE_NAME} started. Polling {_SERVER_COMMAND_URL} every {_POLLING_INTERVAL}s.")
|
117
|
+
while self.is_running:
|
118
|
+
try:
|
119
|
+
self.poll_server_for_commands()
|
120
|
+
except Exception as e:
|
121
|
+
log_error(f"Error in main loop polling cycle: {e}", exc_info=True)
|
122
|
+
|
123
|
+
# Wait for stop event or timeout
|
124
|
+
rc = win32event.WaitForSingleObject(self.hWaitStop, _POLLING_INTERVAL * 1000)
|
125
|
+
if rc == win32event.WAIT_OBJECT_0:
|
126
|
+
# Stop event signaled
|
127
|
+
break
|
128
|
+
log_info(f"{_SERVICE_NAME} is shutting down main loop.")
|
129
|
+
|
130
|
+
|
131
|
+
def poll_server_for_commands(self):
|
132
|
+
log_info(f"Polling server for commands...")
|
133
|
+
try:
|
134
|
+
headers = {'Authorization': f'Bearer {_MACHINE_ID}'} # Example auth
|
135
|
+
# Add machine identifier if needed by server
|
136
|
+
params = {'machine_id': os.getenv('COMPUTERNAME', 'unknown')}
|
137
|
+
response = self.session.get(_SERVER_COMMAND_URL, headers=headers, params=params, timeout=30)
|
138
|
+
response.raise_for_status() # Raise exception for bad status codes
|
139
|
+
|
140
|
+
commands = response.json() # Expecting a list of command objects
|
141
|
+
if not commands:
|
142
|
+
# log_info("No commands received.") # Reduce log noise
|
143
|
+
return
|
144
|
+
|
145
|
+
log_info(f"Received {len(commands)} command(s). Processing...")
|
146
|
+
for command in commands:
|
147
|
+
action = command.get("action")
|
148
|
+
target = command.get("target_user", "all_active") # Default to all
|
149
|
+
command_id = command.get("command_id", "N/A") # Optional: for reporting status
|
150
|
+
|
151
|
+
log_info(f"Processing Command ID {command_id}: action='{action}', target='{target}'")
|
152
|
+
status = "failed" # Default status
|
153
|
+
try:
|
154
|
+
if action == "update":
|
155
|
+
status = self.handle_update()
|
156
|
+
elif action == "stop_ootb":
|
157
|
+
status = self.handle_stop(target)
|
158
|
+
elif action == "start_ootb":
|
159
|
+
status = self.handle_start(target)
|
160
|
+
else:
|
161
|
+
log_error(f"Unknown action received: {action}")
|
162
|
+
status = "unknown_action"
|
163
|
+
except Exception as handler_ex:
|
164
|
+
log_error(f"Error executing action '{action}' for command {command_id}: {handler_ex}", exc_info=True)
|
165
|
+
status = "execution_error"
|
166
|
+
|
167
|
+
# TODO: Add mechanism to report command completion/failure back to server
|
168
|
+
# Example: self.report_command_status(command_id, status)
|
169
|
+
log_info(f"Finished processing Command ID {command_id}: Status='{status}'")
|
170
|
+
|
171
|
+
except requests.exceptions.RequestException as e:
|
172
|
+
log_error(f"Failed to poll server: {e}")
|
173
|
+
except Exception as e:
|
174
|
+
log_error(f"Error processing server commands: {e}", exc_info=True)
|
175
|
+
|
176
|
+
|
177
|
+
def handle_update(self):
|
178
|
+
log_info("Executing OOTB update...")
|
179
|
+
if not self.pip_command_base:
|
180
|
+
log_error("Cannot update: pip command not found.")
|
181
|
+
return "failed_pip_not_found"
|
182
|
+
|
183
|
+
update_command = f"{self.pip_command_base} install --upgrade --no-cache-dir {_PACKAGE_NAME}"
|
184
|
+
log_info(f"Running update command: {update_command}")
|
185
|
+
try:
|
186
|
+
# Run update command
|
187
|
+
result = subprocess.run(update_command, shell=True, capture_output=True, text=True, check=True, timeout=300)
|
188
|
+
log_info(f"Update successful: \nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
|
189
|
+
return "success"
|
190
|
+
except subprocess.CalledProcessError as e:
|
191
|
+
log_error(f"Update failed (Exit Code {e.returncode}):\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
|
192
|
+
return f"failed_exit_{e.returncode}"
|
193
|
+
except subprocess.TimeoutExpired:
|
194
|
+
log_error(f"Update command timed out.")
|
195
|
+
return "failed_timeout"
|
196
|
+
except Exception as e:
|
197
|
+
log_error(f"Unexpected error during update: {e}", exc_info=True)
|
198
|
+
return "failed_exception"
|
199
|
+
|
200
|
+
|
201
|
+
def _get_ootb_processes(self, target_user="all_active"):
|
202
|
+
"""Finds OOTB processes, optionally filtering by username."""
|
203
|
+
ootb_procs = []
|
204
|
+
target_pid_list = []
|
205
|
+
try:
|
206
|
+
# Get a list of usernames we are interested in
|
207
|
+
target_users = set()
|
208
|
+
if target_user == "all_active":
|
209
|
+
for user_session in psutil.users():
|
210
|
+
# Normalize username (remove domain if present)
|
211
|
+
username = user_session.name.split('\\')[-1]
|
212
|
+
target_users.add(username.lower())
|
213
|
+
else:
|
214
|
+
target_users.add(target_user.lower())
|
215
|
+
|
216
|
+
log_info(f"Searching for OOTB processes for users: {target_users}")
|
217
|
+
|
218
|
+
for proc in psutil.process_iter(['pid', 'name', 'username', 'cmdline']):
|
219
|
+
try:
|
220
|
+
pinfo = proc.info
|
221
|
+
# Normalize process username
|
222
|
+
proc_username = pinfo['username']
|
223
|
+
if proc_username:
|
224
|
+
proc_username = proc_username.split('\\')[-1].lower()
|
225
|
+
|
226
|
+
# Check if process user is one of the targets
|
227
|
+
if proc_username in target_users:
|
228
|
+
# Check if command line matches our OOTB app pattern
|
229
|
+
cmdline = ' '.join(pinfo['cmdline']) if pinfo['cmdline'] else ''
|
230
|
+
# Simple check: does it contain python executable and the module name?
|
231
|
+
if (self.python_exe.strip('"') in cmdline) and (_OOTB_MODULE in cmdline):
|
232
|
+
log_info(f"Found matching OOTB process: PID={pinfo['pid']}, User={pinfo['username']}, Cmd={cmdline}")
|
233
|
+
ootb_procs.append(proc)
|
234
|
+
target_pid_list.append(pinfo['pid'])
|
235
|
+
|
236
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
237
|
+
continue # Process might have died or we lack permissions
|
238
|
+
log_info(f"Found {len(ootb_procs)} OOTB process(es) matching criteria: {target_pid_list}")
|
239
|
+
except Exception as e:
|
240
|
+
log_error(f"Error enumerating processes: {e}", exc_info=True)
|
241
|
+
return ootb_procs
|
242
|
+
|
243
|
+
|
244
|
+
def handle_stop(self, target_user="all_active"):
|
245
|
+
log_info(f"Executing stop OOTB for target '{target_user}'...")
|
246
|
+
stopped_count = 0
|
247
|
+
procs_to_stop = self._get_ootb_processes(target_user)
|
248
|
+
|
249
|
+
if not procs_to_stop:
|
250
|
+
log_info("No running OOTB processes found for target.")
|
251
|
+
return "no_process_found"
|
252
|
+
|
253
|
+
for proc in procs_to_stop:
|
254
|
+
try:
|
255
|
+
username = proc.info.get('username', 'unknown_user')
|
256
|
+
log_info(f"Terminating process PID={proc.pid}, User={username}")
|
257
|
+
proc.terminate() # Ask nicely first
|
258
|
+
try:
|
259
|
+
proc.wait(timeout=3) # Wait a bit
|
260
|
+
log_info(f"Process PID={proc.pid} terminated successfully.")
|
261
|
+
stopped_count += 1
|
262
|
+
except psutil.TimeoutExpired:
|
263
|
+
log_warning(f"Process PID={proc.pid} did not terminate gracefully, killing.")
|
264
|
+
proc.kill()
|
265
|
+
stopped_count += 1
|
266
|
+
except psutil.NoSuchProcess:
|
267
|
+
log_info(f"Process PID={proc.pid} already terminated.")
|
268
|
+
stopped_count +=1 # Count it if it disappeared
|
269
|
+
except psutil.AccessDenied:
|
270
|
+
log_error(f"Access denied trying to terminate process PID={proc.pid}. Service might lack privileges?")
|
271
|
+
except Exception as e:
|
272
|
+
log_error(f"Error stopping process PID={proc.pid}: {e}", exc_info=True)
|
273
|
+
|
274
|
+
log_info(f"Finished stopping OOTB. Terminated {stopped_count} process(es).")
|
275
|
+
return f"success_stopped_{stopped_count}"
|
276
|
+
|
277
|
+
|
278
|
+
def handle_start(self, target_user="all_active"):
|
279
|
+
log_info(f"Executing start OOTB for target '{target_user}'...")
|
280
|
+
started_count = 0
|
281
|
+
target_users_started = set()
|
282
|
+
users_failed_to_start = set()
|
283
|
+
|
284
|
+
try:
|
285
|
+
sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
|
286
|
+
active_sessions = {} # Store user: session_id for active sessions
|
287
|
+
|
288
|
+
for session in sessions:
|
289
|
+
# Look for Active sessions, potentially disconnected ones too?
|
290
|
+
# For now, only WTSActive
|
291
|
+
if session['State'] == win32ts.WTSActive:
|
292
|
+
try:
|
293
|
+
user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
|
294
|
+
if user: # Filter out system sessions etc.
|
295
|
+
normalized_user = user.lower()
|
296
|
+
active_sessions[normalized_user] = session['SessionId']
|
297
|
+
except Exception as query_err:
|
298
|
+
log_warning(f"Could not query session {session['SessionId']}: {query_err}")
|
299
|
+
|
300
|
+
log_info(f"Found active user sessions: {active_sessions}")
|
301
|
+
|
302
|
+
target_session_map = {} # user:session_id
|
303
|
+
if target_user == "all_active":
|
304
|
+
target_session_map = active_sessions
|
305
|
+
else:
|
306
|
+
normalized_target = target_user.lower()
|
307
|
+
if normalized_target in active_sessions:
|
308
|
+
target_session_map[normalized_target] = active_sessions[normalized_target]
|
309
|
+
else:
|
310
|
+
log_warning(f"Target user '{target_user}' not found in active sessions.")
|
311
|
+
return "failed_user_not_active"
|
312
|
+
|
313
|
+
if not target_session_map:
|
314
|
+
log_info("No target user sessions found to start OOTB in.")
|
315
|
+
return "failed_no_target_sessions"
|
316
|
+
|
317
|
+
# Check if OOTB is already running for the target users
|
318
|
+
running_procs = self._get_ootb_processes(target_user)
|
319
|
+
users_already_running = set()
|
320
|
+
for proc in running_procs:
|
321
|
+
try:
|
322
|
+
proc_username = proc.info.get('username')
|
323
|
+
if proc_username:
|
324
|
+
users_already_running.add(proc_username.split('\\')[-1].lower())
|
325
|
+
except Exception:
|
326
|
+
pass # Ignore errors getting username here
|
327
|
+
|
328
|
+
log_info(f"Users already running OOTB: {users_already_running}")
|
329
|
+
|
330
|
+
for user, session_id in target_session_map.items():
|
331
|
+
token = None # Ensure token is reset/defined
|
332
|
+
try:
|
333
|
+
if user in users_already_running:
|
334
|
+
log_info(f"OOTB already seems to be running for user '{user}'. Skipping start.")
|
335
|
+
continue
|
336
|
+
|
337
|
+
log_info(f"Attempting to start OOTB for user '{user}' in session {session_id}...")
|
338
|
+
|
339
|
+
# Get user token
|
340
|
+
token = win32ts.WTSQueryUserToken(session_id)
|
341
|
+
|
342
|
+
# Create environment block for the user
|
343
|
+
env = win32profile.CreateEnvironmentBlock(token, False)
|
344
|
+
|
345
|
+
# Create startup info
|
346
|
+
startup = win32process.STARTUPINFO()
|
347
|
+
startup.dwFlags = win32process.STARTF_USESHOWWINDOW
|
348
|
+
# Attempt to show window on user's desktop
|
349
|
+
# Requires Service to have "Allow service to interact with desktop" checked
|
350
|
+
# AND Interactive Services Detection service running (often disabled now).
|
351
|
+
# May be better to run hidden (SW_HIDE) or default.
|
352
|
+
startup.wShowWindow = win32con.SW_SHOW
|
353
|
+
startup.lpDesktop = 'winsta0\\default' # Try targeting default interactive desktop
|
354
|
+
|
355
|
+
# Create process as user
|
356
|
+
# Needs SeAssignPrimaryTokenPrivilege, SeIncreaseQuotaPrivilege for service account.
|
357
|
+
creation_flags = win32process.CREATE_NEW_CONSOLE | win32process.CREATE_UNICODE_ENVIRONMENT
|
358
|
+
|
359
|
+
hProcess, hThread, dwPid, dwTid = win32process.CreateProcessAsUser(
|
360
|
+
token, # User token
|
361
|
+
self.python_exe, # Application name (python executable)
|
362
|
+
self.ootb_command, # Command line
|
363
|
+
None, # Process attributes
|
364
|
+
None, # Thread attributes
|
365
|
+
False, # Inherit handles
|
366
|
+
creation_flags, # Creation flags
|
367
|
+
env, # Environment
|
368
|
+
None, # Current directory (use default)
|
369
|
+
startup # Startup info
|
370
|
+
)
|
371
|
+
log_info(f"Successfully started OOTB for user '{user}' (PID: {dwPid}).")
|
372
|
+
started_count += 1
|
373
|
+
target_users_started.add(user)
|
374
|
+
# Close handles immediately
|
375
|
+
win32api.CloseHandle(hProcess)
|
376
|
+
win32api.CloseHandle(hThread)
|
377
|
+
|
378
|
+
except Exception as proc_err:
|
379
|
+
log_error(f"Failed to start OOTB for user '{user}' in session {session_id}: {proc_err}", exc_info=True)
|
380
|
+
users_failed_to_start.add(user)
|
381
|
+
finally:
|
382
|
+
# Ensure token handle is always closed if obtained
|
383
|
+
if token:
|
384
|
+
try: win32api.CloseHandle(token)
|
385
|
+
except: pass
|
386
|
+
|
387
|
+
|
388
|
+
log_info(f"Finished starting OOTB. Started {started_count} new instance(s). Failed for users: {users_failed_to_start or 'None'}")
|
389
|
+
if users_failed_to_start:
|
390
|
+
return f"partial_success_started_{started_count}_failed_for_{len(users_failed_to_start)}"
|
391
|
+
elif started_count > 0:
|
392
|
+
return f"success_started_{started_count}"
|
393
|
+
else:
|
394
|
+
return "no_action_needed_already_running"
|
395
|
+
|
396
|
+
except Exception as e:
|
397
|
+
log_error(f"Error during start OOTB process: {e}", exc_info=True)
|
398
|
+
return "failed_exception"
|
399
|
+
|
400
|
+
|
401
|
+
# This block is essential for the service framework to handle
|
402
|
+
# command-line arguments like 'install', 'start', 'stop', 'remove', 'debug'.
|
403
|
+
if __name__ == '__main__':
|
404
|
+
# Add logic to allow debugging from command line easily
|
405
|
+
if len(sys.argv) > 1 and sys.argv[1] == 'debug':
|
406
|
+
log_info("Starting service in debug mode...")
|
407
|
+
service_instance = GuardService(sys.argv)
|
408
|
+
service_instance.is_running = True # Ensure loop runs
|
409
|
+
try:
|
410
|
+
# Run the main loop directly for debugging
|
411
|
+
service_instance.main_loop()
|
412
|
+
# Simulate stop signal for clean exit in debug
|
413
|
+
# service_instance.SvcStop() # Or let it run until Ctrl+C?
|
414
|
+
except KeyboardInterrupt:
|
415
|
+
log_info("Debug mode interrupted by user.")
|
416
|
+
service_instance.SvcStop() # Attempt clean stop
|
417
|
+
log_info("Debug mode finished.")
|
418
|
+
elif len(sys.argv) == 1:
|
419
|
+
# Called without arguments, run as a service instance via SCM
|
420
|
+
try:
|
421
|
+
servicemanager.Initialize()
|
422
|
+
servicemanager.PrepareToHostSingle(GuardService)
|
423
|
+
servicemanager.StartServiceCtrlDispatcher()
|
424
|
+
except win32service.error as details:
|
425
|
+
import winerror
|
426
|
+
if details.winerror == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
|
427
|
+
print(f"Error: Cannot connect to Service Control Manager.")
|
428
|
+
print(f"Use 'python {os.path.basename(__file__)} install|start|stop|remove|debug'")
|
429
|
+
else:
|
430
|
+
print(f"Error preparing service: {details}")
|
431
|
+
except Exception as e:
|
432
|
+
print(f"Unexpected error initializing service: {e}")
|
433
|
+
|
434
|
+
else:
|
435
|
+
# Called with install/start/stop/remove args, let ServiceFramework handle them
|
436
|
+
win32serviceutil.HandleCommandLine(GuardService)
|
@@ -0,0 +1 @@
|
|
1
|
+
# This file makes the 'preparation' directory a Python package
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# src/computer_use_ootb_internal/preparation/star_rail_prepare.py
|
2
|
+
import time
|
3
|
+
import platform
|
4
|
+
import pyautogui
|
5
|
+
import webbrowser
|
6
|
+
import logging # Use logging instead of print for better practice
|
7
|
+
|
8
|
+
# Set up logging for this module if needed, or rely on root logger
|
9
|
+
log = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
def run_preparation(state):
|
12
|
+
"""
|
13
|
+
Performs environment preparation specific to Star Rail on Windows.
|
14
|
+
Opens the specified URL in Edge and performs initial clicks.
|
15
|
+
"""
|
16
|
+
if platform.system() != "Windows":
|
17
|
+
log.info("Star Rail preparation skipped: Not running on Windows.")
|
18
|
+
return
|
19
|
+
|
20
|
+
log.info("Star Rail preparation: Starting environment setup on Windows...")
|
21
|
+
url = "https://sr.mihoyo.com/cloud/#/" # Consider making this configurable later
|
22
|
+
browser_opened = False
|
23
|
+
try:
|
24
|
+
# Use only webbrowser.open
|
25
|
+
log.info(f"Attempting to open {url} using webbrowser.open()...")
|
26
|
+
if webbrowser.open(url):
|
27
|
+
log.info(f"Successfully requested browser to open {url} via webbrowser.open().")
|
28
|
+
browser_opened = True
|
29
|
+
else:
|
30
|
+
log.warning("webbrowser.open() returned False, indicating potential failure.")
|
31
|
+
|
32
|
+
if not browser_opened:
|
33
|
+
log.error("Failed to confirm browser opening via webbrowser.open(). Will still attempt clicks.")
|
34
|
+
|
35
|
+
# Add pyautogui click after attempting to open the browser
|
36
|
+
log.info("Proceeding with pyautogui actions...")
|
37
|
+
time.sleep(5) # Wait time for the browser to load
|
38
|
+
|
39
|
+
# Get screen size
|
40
|
+
screen_width, screen_height = pyautogui.size()
|
41
|
+
log.info(f"Detected screen size: {screen_width}x{screen_height}")
|
42
|
+
|
43
|
+
# Calculate click coordinates based on a reference resolution (e.g., 1280x720)
|
44
|
+
# TODO: Make these coordinates more robust or configurable
|
45
|
+
click_x = int(screen_width * (1036 / 1280))
|
46
|
+
click_y = int(screen_height * (500 / 720))
|
47
|
+
log.info(f"Calculated click coordinates: ({click_x}, {click_y})")
|
48
|
+
|
49
|
+
# Disable failsafe before clicking
|
50
|
+
pyautogui.FAILSAFE = False
|
51
|
+
log.info("PyAutoGUI failsafe temporarily disabled.")
|
52
|
+
|
53
|
+
log.info(f"Clicking at coordinates: ({click_x}, {click_y})")
|
54
|
+
pyautogui.click(click_x, click_y)
|
55
|
+
time.sleep(2)
|
56
|
+
pyautogui.click(click_x, click_y) # Double click?
|
57
|
+
|
58
|
+
log.info("Star Rail preparation clicks completed.")
|
59
|
+
|
60
|
+
except Exception as e:
|
61
|
+
log.error(f"Error during Star Rail preparation (browser/click): {e}", exc_info=True)
|
62
|
+
finally:
|
63
|
+
# Ensure failsafe is re-enabled
|
64
|
+
pyautogui.FAILSAFE = True
|
65
|
+
log.info("PyAutoGUI failsafe re-enabled.")
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# src/computer_use_ootb_internal/service_manager.py
|
2
|
+
import sys
|
3
|
+
import os
|
4
|
+
import inspect
|
5
|
+
import subprocess
|
6
|
+
import ctypes
|
7
|
+
import platform
|
8
|
+
import time
|
9
|
+
|
10
|
+
# Constants need to match guard_service.py
|
11
|
+
_SERVICE_NAME = "OOTBGuardService"
|
12
|
+
_SERVICE_DISPLAY_NAME = "OOTB Guard Service"
|
13
|
+
|
14
|
+
def is_admin():
|
15
|
+
"""Check if the script is running with administrative privileges."""
|
16
|
+
if platform.system() != "Windows":
|
17
|
+
return False # Only applicable on Windows
|
18
|
+
try:
|
19
|
+
return ctypes.windll.shell32.IsUserAnAdmin()
|
20
|
+
except:
|
21
|
+
return False
|
22
|
+
|
23
|
+
def get_service_module_path():
|
24
|
+
"""Gets the absolute path to the guard_service.py module."""
|
25
|
+
# Find the path relative to this script's location
|
26
|
+
# This assumes service_manager.py and guard_service.py are in the same installed package directory
|
27
|
+
try:
|
28
|
+
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
29
|
+
service_module = os.path.join(current_dir, "guard_service.py")
|
30
|
+
if not os.path.exists(service_module):
|
31
|
+
raise FileNotFoundError(f"guard_service.py not found adjacent to service_manager.py in {current_dir}")
|
32
|
+
return service_module
|
33
|
+
except Exception as e:
|
34
|
+
# Fallback if inspect fails (e.g., in some frozen environments)
|
35
|
+
# Try finding it relative to the script itself? Unreliable.
|
36
|
+
# Let's try sys.prefix - might work in standard venv/conda installs
|
37
|
+
try:
|
38
|
+
# sys.prefix points to the environment root (e.g., C:\path\to\env)
|
39
|
+
# Package likely installed in Lib\site-packages\<package_name>
|
40
|
+
# This depends heavily on installation layout
|
41
|
+
package_name = __name__.split('.')[0] # Should be 'computer_use_ootb_internal'
|
42
|
+
site_packages_path = os.path.join(sys.prefix, 'Lib', 'site-packages')
|
43
|
+
module_dir = os.path.join(site_packages_path, package_name)
|
44
|
+
service_module = os.path.join(module_dir, "guard_service.py")
|
45
|
+
if os.path.exists(service_module):
|
46
|
+
print(f"Warning: Found service module via sys.prefix fallback: {service_module}")
|
47
|
+
return service_module
|
48
|
+
else:
|
49
|
+
raise FileNotFoundError(f"guard_service.py not found via inspect or sys.prefix fallback (checked {module_dir})")
|
50
|
+
except Exception as fallback_e:
|
51
|
+
raise FileNotFoundError(f"Could not find guard_service.py using inspect ({e}) or sys.prefix ({fallback_e}). Check installation.")
|
52
|
+
|
53
|
+
|
54
|
+
def run_service_command(command_args, check_errors=True):
|
55
|
+
"""Runs the guard_service.py script with specified command-line args."""
|
56
|
+
if not is_admin():
|
57
|
+
print("Error: Administrative privileges are required to manage the service.", file=sys.stderr)
|
58
|
+
print("Please run this command from an Administrator Command Prompt or PowerShell.", file=sys.stderr)
|
59
|
+
return False
|
60
|
+
|
61
|
+
try:
|
62
|
+
python_exe = sys.executable # Use the same python that's running this script
|
63
|
+
service_script = get_service_module_path()
|
64
|
+
except FileNotFoundError as e:
|
65
|
+
print(f"Error: {e}", file=sys.stderr)
|
66
|
+
return False
|
67
|
+
|
68
|
+
# Quote paths if they contain spaces
|
69
|
+
if " " in python_exe and not python_exe.startswith('"'):
|
70
|
+
python_exe = f'"{python_exe}"'
|
71
|
+
if " " in service_script and not service_script.startswith('"'):
|
72
|
+
service_script = f'"{service_script}"'
|
73
|
+
|
74
|
+
# Construct command using list to avoid shell quoting issues
|
75
|
+
cmd = [sys.executable, get_service_module_path()] + command_args
|
76
|
+
print(f"Executing command: {' '.join(cmd)}")
|
77
|
+
|
78
|
+
try:
|
79
|
+
# Run the command. Use shell=False with list of args.
|
80
|
+
# Capture output to check for specific errors if needed, but print it too.
|
81
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=check_errors, encoding='utf-8')
|
82
|
+
if result.stdout:
|
83
|
+
print("Command STDOUT:")
|
84
|
+
print(result.stdout)
|
85
|
+
if result.stderr:
|
86
|
+
print("Command STDERR:")
|
87
|
+
print(result.stderr)
|
88
|
+
print(f"Command {' '.join(command_args)} executed successfully.")
|
89
|
+
return True
|
90
|
+
except FileNotFoundError as e:
|
91
|
+
print(f"Error: Could not find Python executable or service script during execution.", file=sys.stderr)
|
92
|
+
print(f" Details: {e}", file=sys.stderr)
|
93
|
+
return False
|
94
|
+
except subprocess.CalledProcessError as e:
|
95
|
+
print(f"Error executing service command {' '.join(command_args)} (Exit Code {e.returncode}).", file=sys.stderr)
|
96
|
+
if e.stdout:
|
97
|
+
print("Subprocess STDOUT:")
|
98
|
+
print(e.stdout)
|
99
|
+
if e.stderr:
|
100
|
+
print("Subprocess STDERR:")
|
101
|
+
print(e.stderr)
|
102
|
+
return False
|
103
|
+
except Exception as e:
|
104
|
+
print(f"An unexpected error occurred running service command: {e}", file=sys.stderr)
|
105
|
+
return False
|
106
|
+
|
107
|
+
|
108
|
+
def install_and_start():
|
109
|
+
"""Installs and starts the Guard Service."""
|
110
|
+
print(f"Attempting to install/update service: '{_SERVICE_NAME}' ('{_SERVICE_DISPLAY_NAME}')")
|
111
|
+
# Use 'update' which handles install or updates display name/path if needed
|
112
|
+
if run_service_command(['--startup', 'auto', 'update']): # Set startup to auto, then update
|
113
|
+
print(f"\nAttempting to start service: '{_SERVICE_NAME}' (waiting a few seconds first)")
|
114
|
+
time.sleep(3) # Give SCM time to register the update
|
115
|
+
if run_service_command(['start']):
|
116
|
+
print(f"\nService '{_SERVICE_NAME}' installed/updated and started successfully.")
|
117
|
+
else:
|
118
|
+
print(f"\nWarning: Service '{_SERVICE_NAME}' installed/updated but failed to start.", file=sys.stderr)
|
119
|
+
print(f" Check service logs ('C:\ProgramData\OOTBGuardService\guard.log') or Windows Event Viewer for details.", file=sys.stderr)
|
120
|
+
print(f" You may need to start it manually via 'services.msc' or `net start {_SERVICE_NAME}`.")
|
121
|
+
else:
|
122
|
+
print(f"\nService '{_SERVICE_NAME}' installation/update failed.", file=sys.stderr)
|
123
|
+
|
124
|
+
|
125
|
+
def stop_and_remove():
|
126
|
+
"""Stops and removes the Guard Service."""
|
127
|
+
print(f"Attempting to stop service: '{_SERVICE_NAME}' (will ignore errors if not running)")
|
128
|
+
# Run stop first, ignore errors (check_errors=False)
|
129
|
+
run_service_command(['stop'], check_errors=False)
|
130
|
+
time.sleep(2) # Give service time to stop
|
131
|
+
|
132
|
+
print(f"\nAttempting to remove service: '{_SERVICE_NAME}'")
|
133
|
+
if run_service_command(['remove']):
|
134
|
+
print(f"\nService '{_SERVICE_NAME}' stopped (if running) and removed successfully.")
|
135
|
+
else:
|
136
|
+
print(f"\nService '{_SERVICE_NAME}' removal failed.", file=sys.stderr)
|
137
|
+
print(f" Ensure the service was stopped first, or check permissions.", file=sys.stderr)
|
138
|
+
|
139
|
+
if __name__ == '__main__':
|
140
|
+
# Allow calling functions directly for testing if needed
|
141
|
+
print("This script provides service management commands.")
|
142
|
+
print("Use 'ootb-install-service' or 'ootb-remove-service' as Administrator.")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: computer-use-ootb-internal
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.112
|
4
4
|
Summary: Computer Use OOTB
|
5
5
|
Author-email: Siyuan Hu <siyuan.hu.sg@gmail.com>
|
6
6
|
Requires-Python: >=3.11
|
@@ -12,17 +12,17 @@ Requires-Dist: jsonschema==4.22.0
|
|
12
12
|
Requires-Dist: litellm
|
13
13
|
Requires-Dist: matplotlib
|
14
14
|
Requires-Dist: opencv-python
|
15
|
-
Requires-Dist:
|
15
|
+
Requires-Dist: psutil>=5.0; sys_platform == 'win32'
|
16
16
|
Requires-Dist: pyautogui==0.9.54
|
17
|
-
Requires-Dist:
|
18
|
-
Requires-Dist: pytest==8.3.3
|
17
|
+
Requires-Dist: pywin32>=306; sys_platform == 'win32'
|
19
18
|
Requires-Dist: pywinauto; sys_platform == 'win32'
|
20
|
-
Requires-Dist:
|
19
|
+
Requires-Dist: requests>=2.0
|
21
20
|
Requires-Dist: screeninfo
|
22
21
|
Requires-Dist: streamlit>=1.38.0
|
23
22
|
Requires-Dist: textdistance
|
24
23
|
Requires-Dist: uiautomation; sys_platform == 'win32'
|
25
24
|
Provides-Extra: dev
|
25
|
+
Requires-Dist: pre-commit>=3.8.0; extra == 'dev'
|
26
26
|
Requires-Dist: pytest-asyncio>=0.23.6; extra == 'dev'
|
27
27
|
Requires-Dist: pytest>=8.3.3; extra == 'dev'
|
28
28
|
Requires-Dist: ruff>=0.6.7; extra == 'dev'
|
{computer_use_ootb_internal-0.0.110.dist-info → computer_use_ootb_internal-0.0.112.dist-info}/RECORD
RENAMED
@@ -1,11 +1,12 @@
|
|
1
1
|
computer_use_ootb_internal/README.md,sha256=FxpW95lyub2iX73ZDfK6ML7SdEKg060H5I6Grub7li4,31
|
2
2
|
computer_use_ootb_internal/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
3
|
-
computer_use_ootb_internal/app_teachmode.py,sha256
|
3
|
+
computer_use_ootb_internal/app_teachmode.py,sha256=U0vHAEHeDMDioYatLQkzesT1Hy0aI6kKjhAlV86zxfc,17902
|
4
4
|
computer_use_ootb_internal/app_teachmode_gradio.py,sha256=cmFpBrkdlZxOQADWveVdIaaNqaBD8IVs-xNLJogU7F8,7909
|
5
5
|
computer_use_ootb_internal/dependency_check.py,sha256=y8RMEP6RXQzTgU1MS_1piBLtz4J-Hfn9RjUZg59dyvo,1333
|
6
|
+
computer_use_ootb_internal/guard_service.py,sha256=bTzBQMnHYaUXBwCWcnn0cXBPJyKl26-LVRIRB-fiRzo,20558
|
6
7
|
computer_use_ootb_internal/requirements-lite.txt,sha256=5DAHomz4A_P2BmTIXNkNqkHbnIF0AyZ4_1XAlb1LaYs,290
|
7
8
|
computer_use_ootb_internal/run_teachmode_ootb_args.py,sha256=7Dj0iY4GG7P03tRKYJ2x9Yvt-PE-b7uyjCAed3SaF3Y,7086
|
8
|
-
computer_use_ootb_internal/
|
9
|
+
computer_use_ootb_internal/service_manager.py,sha256=sesbSUBBqm6W77Dykaw-HGARU-yNdK9DqOHPiR2TbkE,6920
|
9
10
|
computer_use_ootb_internal/computer_use_demo/animation/click_animation.py,sha256=tP1gsayFy-CKk10UlrE9RlexwlHWiHQUe5Ogg4vQvSg,3234
|
10
11
|
computer_use_ootb_internal/computer_use_demo/animation/icons8-select-cursor-transparent-96.gif,sha256=4LfwsfFQnREXrNRs32aJU2jO65JXianJoL_8q7-8elg,30966
|
11
12
|
computer_use_ootb_internal/computer_use_demo/animation/test_animation.py,sha256=2R1u98OLKYalSZ5nt5vvyZ71FL5R5vLv-n8zM8jVdV8,1183
|
@@ -31,7 +32,9 @@ computer_use_ootb_internal/computer_use_demo/tools/computer_marbot.py,sha256=zZu
|
|
31
32
|
computer_use_ootb_internal/computer_use_demo/tools/edit.py,sha256=b0PwUitxckHCQqFP3ZwlthWdqNkn7WETeTHeB6-o98c,11486
|
32
33
|
computer_use_ootb_internal/computer_use_demo/tools/run.py,sha256=xhXdnBK1di9muaO44CEirL9hpGy3NmKbjfMpyeVmn8Y,1595
|
33
34
|
computer_use_ootb_internal/computer_use_demo/tools/screen_capture.py,sha256=L8qfvtUkPPQGt92N-2Zfw5ZTDBzLsDps39uMnX3_uSA,6857
|
34
|
-
computer_use_ootb_internal
|
35
|
-
computer_use_ootb_internal
|
36
|
-
computer_use_ootb_internal-0.0.
|
37
|
-
computer_use_ootb_internal-0.0.
|
35
|
+
computer_use_ootb_internal/preparation/__init__.py,sha256=AgtGHcBpiTkxJjF0xwcs3yyQ6SyUvhL3G0vD2XO-zJw,63
|
36
|
+
computer_use_ootb_internal/preparation/star_rail_prepare.py,sha256=s1VWszcTnJAKxqCHFlaOEwPkqVSrkiFx_yKpWSnSbHs,2649
|
37
|
+
computer_use_ootb_internal-0.0.112.dist-info/METADATA,sha256=P4zVajg7g2i1wpHYk98DU4M9JawiqWp4mKz7crW9qSM,993
|
38
|
+
computer_use_ootb_internal-0.0.112.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
39
|
+
computer_use_ootb_internal-0.0.112.dist-info/entry_points.txt,sha256=bXfyAU_qq-G1EiEgAQEioXvgEdRCFxaTooqdDD9Y4OA,258
|
40
|
+
computer_use_ootb_internal-0.0.112.dist-info/RECORD,,
|
@@ -1,194 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
import os
|
3
|
-
import winreg
|
4
|
-
import ctypes
|
5
|
-
import platform
|
6
|
-
import shutil
|
7
|
-
import subprocess
|
8
|
-
import pathlib
|
9
|
-
|
10
|
-
REG_PATH = r"Software\Microsoft\Windows\CurrentVersion\Run"
|
11
|
-
APP_NAME = "OOTBLite" # Or a more specific name for your app's startup entry
|
12
|
-
# The package name as installed by pip
|
13
|
-
PACKAGE_NAME = "computer-use-ootb-internal"
|
14
|
-
# The main module to run at startup
|
15
|
-
STARTUP_MODULE = "computer_use_ootb_internal.app_teachmode"
|
16
|
-
# Name for the scheduled task
|
17
|
-
TASK_NAME = "OOTBLite_AutoUpdate"
|
18
|
-
|
19
|
-
def is_admin():
|
20
|
-
"""Check if the script is running with administrative privileges."""
|
21
|
-
try:
|
22
|
-
return ctypes.windll.shell32.IsUserAnAdmin()
|
23
|
-
except:
|
24
|
-
return False
|
25
|
-
|
26
|
-
def get_python_executable():
|
27
|
-
"""Gets the quoted path to the current python executable."""
|
28
|
-
python_exe = sys.executable
|
29
|
-
if " " in python_exe and not python_exe.startswith('"'):
|
30
|
-
python_exe = f'"{python_exe}"'
|
31
|
-
return python_exe
|
32
|
-
|
33
|
-
def get_pip_executable():
|
34
|
-
"""Tries to locate the pip executable in the same environment."""
|
35
|
-
python_path = pathlib.Path(sys.executable)
|
36
|
-
# Common location is ../Scripts/pip.exe relative to python.exe
|
37
|
-
pip_path = python_path.parent / "Scripts" / "pip.exe"
|
38
|
-
if pip_path.exists():
|
39
|
-
# Quote if necessary
|
40
|
-
pip_exe = str(pip_path)
|
41
|
-
if " " in pip_exe and not pip_exe.startswith('"'):
|
42
|
-
pip_exe = f'"{pip_exe}"'
|
43
|
-
return pip_exe
|
44
|
-
else:
|
45
|
-
# Fallback: try using 'python -m pip'
|
46
|
-
print("Warning: pip.exe not found in Scripts directory. Falling back to 'python -m pip'.", file=sys.stderr)
|
47
|
-
return f"{get_python_executable()} -m pip"
|
48
|
-
|
49
|
-
def run_powershell_command(command):
|
50
|
-
"""Executes a PowerShell command and handles output/errors."""
|
51
|
-
try:
|
52
|
-
# Use powershell.exe - it's more universally available than pwsh.exe
|
53
|
-
# capture_output=True suppresses output unless there's an error
|
54
|
-
# text=True decodes output/error streams
|
55
|
-
# check=True raises CalledProcessError on non-zero exit codes
|
56
|
-
result = subprocess.run(
|
57
|
-
["powershell.exe", "-NoProfile", "-Command", command],
|
58
|
-
capture_output=True, text=True, check=True, encoding='utf-8'
|
59
|
-
)
|
60
|
-
print(f"PowerShell command executed successfully.")
|
61
|
-
if result.stdout:
|
62
|
-
print("Output:\n", result.stdout)
|
63
|
-
return True
|
64
|
-
except FileNotFoundError:
|
65
|
-
print("Error: 'powershell.exe' not found. Cannot manage scheduled tasks.", file=sys.stderr)
|
66
|
-
return False
|
67
|
-
except subprocess.CalledProcessError as e:
|
68
|
-
print(f"Error executing PowerShell command:", file=sys.stderr)
|
69
|
-
print(f" Command: {e.cmd}", file=sys.stderr)
|
70
|
-
print(f" Exit Code: {e.returncode}", file=sys.stderr)
|
71
|
-
print(f" Stderr: {e.stderr}", file=sys.stderr)
|
72
|
-
print(f" Stdout: {e.stdout}", file=sys.stderr)
|
73
|
-
return False
|
74
|
-
except Exception as e:
|
75
|
-
print(f"An unexpected error occurred running PowerShell: {e}", file=sys.stderr)
|
76
|
-
return False
|
77
|
-
|
78
|
-
|
79
|
-
def configure_startup():
|
80
|
-
"""Adds the application to Windows startup and sets up auto-update task."""
|
81
|
-
if platform.system() != "Windows":
|
82
|
-
print("Error: This utility is only for Windows.", file=sys.stderr)
|
83
|
-
sys.exit(1)
|
84
|
-
|
85
|
-
if not is_admin():
|
86
|
-
print("Error: This utility requires administrative privileges.", file=sys.stderr)
|
87
|
-
print("Please run this command from an Administrator Command Prompt or PowerShell.", file=sys.stderr)
|
88
|
-
sys.exit(1)
|
89
|
-
|
90
|
-
# 1. Configure Registry for Startup
|
91
|
-
print("Configuring registry for application startup...")
|
92
|
-
python_exe = get_python_executable()
|
93
|
-
startup_command = f'{python_exe} -m {STARTUP_MODULE}'
|
94
|
-
try:
|
95
|
-
key = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, REG_PATH)
|
96
|
-
winreg.SetValueEx(key, APP_NAME, 0, winreg.REG_SZ, startup_command)
|
97
|
-
winreg.CloseKey(key)
|
98
|
-
print(f"Success: Registry key HKLM\{REG_PATH}\{APP_NAME} set.")
|
99
|
-
print(f" Command: {startup_command}")
|
100
|
-
except OSError as e:
|
101
|
-
print(f"Error: Failed to set registry key HKLM\{REG_PATH}\{APP_NAME}", file=sys.stderr)
|
102
|
-
print(f"Details: {e}", file=sys.stderr)
|
103
|
-
# Continue to task setup even if registry fails? Or exit? Let's exit for now.
|
104
|
-
sys.exit(1)
|
105
|
-
except Exception as e:
|
106
|
-
print(f"An unexpected error occurred setting registry key: {e}", file=sys.stderr)
|
107
|
-
sys.exit(1)
|
108
|
-
|
109
|
-
# 2. Configure Scheduled Task for Auto-Update
|
110
|
-
print("\nConfiguring scheduled task for automatic updates...")
|
111
|
-
pip_command = get_pip_executable()
|
112
|
-
if not pip_command:
|
113
|
-
print("Error: Could not determine pip command. Skipping scheduled task setup.", file=sys.stderr)
|
114
|
-
sys.exit(1) # Exit if we can't find pip
|
115
|
-
|
116
|
-
update_command_args = f'install --upgrade --no-cache-dir {PACKAGE_NAME}'
|
117
|
-
# Need to handle quoting args for PowerShell if pip_command includes python.exe
|
118
|
-
pip_exe_path = pip_command.split()[0]
|
119
|
-
if "-m pip" in pip_command:
|
120
|
-
ps_args = f"-m pip {update_command_args}"
|
121
|
-
else:
|
122
|
-
ps_args = update_command_args
|
123
|
-
|
124
|
-
# PowerShell commands to create the task
|
125
|
-
# Define Action, Trigger, Principal, Settings, and then Register
|
126
|
-
# Note: Escaping quotes for PowerShell within Python string
|
127
|
-
# Ensure executable path and arguments are properly quoted for PowerShell
|
128
|
-
# Use triple-double quotes for f-strings containing single-quoted PowerShell strings
|
129
|
-
action = f"""$Action = New-ScheduledTaskAction -Execute '{pip_exe_path}' -Argument '{ps_args.replace("'", "''")}'""" # Escape single quotes in args for PS string
|
130
|
-
# Trigger: Daily, repeat every hour indefinitely
|
131
|
-
trigger = f"""$Trigger = New-ScheduledTaskTrigger -Daily -At 3am; $Trigger.Repetition.Interval = 'PT1H'; $Trigger.Repetition.Duration = 'P10675199D'""" # Using large duration for 'indefinitely'
|
132
|
-
# Principal: Run as SYSTEM user
|
133
|
-
principal = f"""$Principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest"""
|
134
|
-
# Settings: Allow start if on batteries, don't stop if goes off batteries (adjust as needed)
|
135
|
-
settings = f"""$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable""" # Added StartWhenAvailable
|
136
|
-
|
137
|
-
# Register Task: Use -Force to overwrite if it exists
|
138
|
-
description = f"Hourly check for {PACKAGE_NAME} updates."
|
139
|
-
# Escape single quotes in description just in case PACKAGE_NAME contains them
|
140
|
-
escaped_description = description.replace("'", "''")
|
141
|
-
register_command = f"""{action}; {trigger}; {principal}; {settings}; Register-ScheduledTask -TaskName '{TASK_NAME}' -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Force -Description '{escaped_description}'"""
|
142
|
-
|
143
|
-
print(f"Attempting to create/update scheduled task '{TASK_NAME}'...")
|
144
|
-
if run_powershell_command(register_command):
|
145
|
-
print(f"Success: Scheduled task '{TASK_NAME}' created/updated.")
|
146
|
-
print(f" Task Action: {pip_exe_path} {ps_args}")
|
147
|
-
else:
|
148
|
-
print(f"Error: Failed to configure scheduled task '{TASK_NAME}'. Please check PowerShell errors above.", file=sys.stderr)
|
149
|
-
# Decide if failure here is critical
|
150
|
-
# sys.exit(1)
|
151
|
-
|
152
|
-
|
153
|
-
def remove_startup():
|
154
|
-
"""Removes the application from Windows startup and removes auto-update task."""
|
155
|
-
if platform.system() != "Windows":
|
156
|
-
print("Error: This utility is only for Windows.", file=sys.stderr)
|
157
|
-
sys.exit(1)
|
158
|
-
|
159
|
-
if not is_admin():
|
160
|
-
print("Error: This utility requires administrative privileges.", file=sys.stderr)
|
161
|
-
print("Please run this command from an Administrator Command Prompt or PowerShell.", file=sys.stderr)
|
162
|
-
sys.exit(1)
|
163
|
-
|
164
|
-
# 1. Remove Registry Key
|
165
|
-
print("Removing registry key...")
|
166
|
-
try:
|
167
|
-
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, REG_PATH, 0, winreg.KEY_WRITE)
|
168
|
-
winreg.DeleteValue(key, APP_NAME)
|
169
|
-
winreg.CloseKey(key)
|
170
|
-
print(f"Success: Registry key HKLM\{REG_PATH}\{APP_NAME} removed.")
|
171
|
-
except FileNotFoundError:
|
172
|
-
print(f"Info: Registry key HKLM\{REG_PATH}\{APP_NAME} not found. No action taken.")
|
173
|
-
except OSError as e:
|
174
|
-
print(f"Error: Failed to delete registry key HKLM\{REG_PATH}\{APP_NAME}", file=sys.stderr)
|
175
|
-
print(f"Details: {e}", file=sys.stderr)
|
176
|
-
# Continue to task removal
|
177
|
-
except Exception as e:
|
178
|
-
print(f"An unexpected error occurred removing registry key: {e}", file=sys.stderr)
|
179
|
-
# Continue to task removal
|
180
|
-
|
181
|
-
# 2. Remove Scheduled Task
|
182
|
-
print(f"\nRemoving scheduled task '{TASK_NAME}'...")
|
183
|
-
# Use -ErrorAction SilentlyContinue for Unregister-ScheduledTask if it might not exist
|
184
|
-
# Use triple-double quotes for f-string
|
185
|
-
unregister_command = f"""Unregister-ScheduledTask -TaskName '{TASK_NAME}' -Confirm:$false -ErrorAction SilentlyContinue"""
|
186
|
-
|
187
|
-
if run_powershell_command(unregister_command):
|
188
|
-
print(f"Success: Scheduled task '{TASK_NAME}' removed (if it existed).")
|
189
|
-
else:
|
190
|
-
print(f"Error: Failed to remove scheduled task '{TASK_NAME}'. Please check PowerShell errors above.", file=sys.stderr)
|
191
|
-
|
192
|
-
if __name__ == '__main__':
|
193
|
-
print("This script provides startup and auto-update configuration utilities.")
|
194
|
-
print("Use 'ootb-configure-startup' or 'ootb-remove-startup' as Administrator after installation.")
|
{computer_use_ootb_internal-0.0.110.dist-info → computer_use_ootb_internal-0.0.112.dist-info}/WHEEL
RENAMED
File without changes
|