lybic-guiagents 0.1.0__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.

Potentially problematic release.


This version of lybic-guiagents might be problematic. Click here for more details.

Files changed (85) hide show
  1. desktop_env/__init__.py +1 -0
  2. desktop_env/actions.py +203 -0
  3. desktop_env/controllers/__init__.py +0 -0
  4. desktop_env/controllers/python.py +471 -0
  5. desktop_env/controllers/setup.py +882 -0
  6. desktop_env/desktop_env.py +509 -0
  7. desktop_env/evaluators/__init__.py +5 -0
  8. desktop_env/evaluators/getters/__init__.py +41 -0
  9. desktop_env/evaluators/getters/calc.py +15 -0
  10. desktop_env/evaluators/getters/chrome.py +1774 -0
  11. desktop_env/evaluators/getters/file.py +154 -0
  12. desktop_env/evaluators/getters/general.py +42 -0
  13. desktop_env/evaluators/getters/gimp.py +38 -0
  14. desktop_env/evaluators/getters/impress.py +126 -0
  15. desktop_env/evaluators/getters/info.py +24 -0
  16. desktop_env/evaluators/getters/misc.py +406 -0
  17. desktop_env/evaluators/getters/replay.py +20 -0
  18. desktop_env/evaluators/getters/vlc.py +86 -0
  19. desktop_env/evaluators/getters/vscode.py +35 -0
  20. desktop_env/evaluators/metrics/__init__.py +160 -0
  21. desktop_env/evaluators/metrics/basic_os.py +68 -0
  22. desktop_env/evaluators/metrics/chrome.py +493 -0
  23. desktop_env/evaluators/metrics/docs.py +1011 -0
  24. desktop_env/evaluators/metrics/general.py +665 -0
  25. desktop_env/evaluators/metrics/gimp.py +637 -0
  26. desktop_env/evaluators/metrics/libreoffice.py +28 -0
  27. desktop_env/evaluators/metrics/others.py +92 -0
  28. desktop_env/evaluators/metrics/pdf.py +31 -0
  29. desktop_env/evaluators/metrics/slides.py +957 -0
  30. desktop_env/evaluators/metrics/table.py +585 -0
  31. desktop_env/evaluators/metrics/thunderbird.py +176 -0
  32. desktop_env/evaluators/metrics/utils.py +719 -0
  33. desktop_env/evaluators/metrics/vlc.py +524 -0
  34. desktop_env/evaluators/metrics/vscode.py +283 -0
  35. desktop_env/providers/__init__.py +35 -0
  36. desktop_env/providers/aws/__init__.py +0 -0
  37. desktop_env/providers/aws/manager.py +278 -0
  38. desktop_env/providers/aws/provider.py +186 -0
  39. desktop_env/providers/aws/provider_with_proxy.py +315 -0
  40. desktop_env/providers/aws/proxy_pool.py +193 -0
  41. desktop_env/providers/azure/__init__.py +0 -0
  42. desktop_env/providers/azure/manager.py +87 -0
  43. desktop_env/providers/azure/provider.py +207 -0
  44. desktop_env/providers/base.py +97 -0
  45. desktop_env/providers/gcp/__init__.py +0 -0
  46. desktop_env/providers/gcp/manager.py +0 -0
  47. desktop_env/providers/gcp/provider.py +0 -0
  48. desktop_env/providers/virtualbox/__init__.py +0 -0
  49. desktop_env/providers/virtualbox/manager.py +463 -0
  50. desktop_env/providers/virtualbox/provider.py +124 -0
  51. desktop_env/providers/vmware/__init__.py +0 -0
  52. desktop_env/providers/vmware/manager.py +455 -0
  53. desktop_env/providers/vmware/provider.py +105 -0
  54. gui_agents/__init__.py +0 -0
  55. gui_agents/agents/Action.py +209 -0
  56. gui_agents/agents/__init__.py +0 -0
  57. gui_agents/agents/agent_s.py +832 -0
  58. gui_agents/agents/global_state.py +610 -0
  59. gui_agents/agents/grounding.py +651 -0
  60. gui_agents/agents/hardware_interface.py +129 -0
  61. gui_agents/agents/manager.py +568 -0
  62. gui_agents/agents/translator.py +132 -0
  63. gui_agents/agents/worker.py +355 -0
  64. gui_agents/cli_app.py +560 -0
  65. gui_agents/core/__init__.py +0 -0
  66. gui_agents/core/engine.py +1496 -0
  67. gui_agents/core/knowledge.py +449 -0
  68. gui_agents/core/mllm.py +555 -0
  69. gui_agents/tools/__init__.py +0 -0
  70. gui_agents/tools/tools.py +727 -0
  71. gui_agents/unit_test/__init__.py +0 -0
  72. gui_agents/unit_test/run_tests.py +65 -0
  73. gui_agents/unit_test/test_manager.py +330 -0
  74. gui_agents/unit_test/test_worker.py +269 -0
  75. gui_agents/utils/__init__.py +0 -0
  76. gui_agents/utils/analyze_display.py +301 -0
  77. gui_agents/utils/common_utils.py +263 -0
  78. gui_agents/utils/display_viewer.py +281 -0
  79. gui_agents/utils/embedding_manager.py +53 -0
  80. gui_agents/utils/image_axis_utils.py +27 -0
  81. lybic_guiagents-0.1.0.dist-info/METADATA +416 -0
  82. lybic_guiagents-0.1.0.dist-info/RECORD +85 -0
  83. lybic_guiagents-0.1.0.dist-info/WHEEL +5 -0
  84. lybic_guiagents-0.1.0.dist-info/licenses/LICENSE +201 -0
  85. lybic_guiagents-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,455 @@
1
+ import os
2
+ import platform
3
+ import random
4
+ import re
5
+
6
+ import threading
7
+ from filelock import FileLock
8
+ import uuid
9
+ import zipfile
10
+
11
+ from time import sleep
12
+ import shutil
13
+ import psutil
14
+ import subprocess
15
+ import requests
16
+ from tqdm import tqdm
17
+
18
+ import logging
19
+
20
+ from desktop_env.providers.base import VMManager
21
+
22
+ logger = logging.getLogger("desktopenv.providers.vmware.VMwareVMManager")
23
+ logger.setLevel(logging.INFO)
24
+
25
+ MAX_RETRY_TIMES = 10
26
+ RETRY_INTERVAL = 5
27
+ UBUNTU_ARM_URL = "https://huggingface.co/datasets/xlangai/ubuntu_osworld/resolve/main/Ubuntu-arm.zip"
28
+ UBUNTU_X86_URL = "https://huggingface.co/datasets/xlangai/ubuntu_osworld/resolve/main/Ubuntu-x86.zip"
29
+ WINDOWS_X86_URL = "https://huggingface.co/datasets/xlangai/windows_osworld/resolve/main/Windows-x86.zip"
30
+
31
+ # Determine the platform and CPU architecture to decide the correct VM image to download
32
+ if platform.system() == 'Darwin': # macOS
33
+ # if os.uname().machine == 'arm64': # Apple Silicon
34
+ URL = UBUNTU_ARM_URL
35
+ # else:
36
+ # url = UBUNTU_X86_URL
37
+ elif platform.machine().lower() in ['amd64', 'x86_64']:
38
+ URL = UBUNTU_X86_URL
39
+ else:
40
+ raise Exception("Unsupported platform or architecture")
41
+
42
+ DOWNLOADED_FILE_NAME = URL.split('/')[-1]
43
+ REGISTRY_PATH = '.vmware_vms'
44
+ LOCK_FILE_NAME = '.vmware_lck'
45
+ VMS_DIR = "vmware_vm_data"
46
+ update_lock = threading.Lock()
47
+
48
+ if platform.system() == 'Windows':
49
+ vboxmanage_path = r"C:\Program Files (x86)\VMware\VMware Workstation"
50
+ os.environ["PATH"] += os.pathsep + vboxmanage_path
51
+
52
+ def generate_new_vm_name(vms_dir, os_type):
53
+ registry_idx = 0
54
+ prefix = os_type
55
+ while True:
56
+ attempted_new_name = f"{prefix}{registry_idx}"
57
+ if os.path.exists(
58
+ os.path.join(vms_dir, attempted_new_name, attempted_new_name + ".vmx")):
59
+ registry_idx += 1
60
+ else:
61
+ return attempted_new_name
62
+
63
+
64
+ def _update_vm(vmx_path, target_vm_name):
65
+ """Update the VMX file with the new VM name and other parameters, so that the VM can be started successfully without conflict with the original VM."""
66
+ with update_lock:
67
+ dir_path, vmx_file = os.path.split(vmx_path)
68
+
69
+ def _generate_mac_address():
70
+ # VMware MAC address range starts with 00:0c:29
71
+ mac = [0x00, 0x0c, 0x29,
72
+ random.randint(0x00, 0x7f),
73
+ random.randint(0x00, 0xff),
74
+ random.randint(0x00, 0xff)]
75
+ return ':'.join(map(lambda x: "%02x" % x, mac))
76
+
77
+ # Backup the original file
78
+ with open(vmx_path, 'r') as file:
79
+ original_content = file.read()
80
+
81
+ # Generate new values
82
+ new_uuid_bios = str(uuid.uuid4())
83
+ new_uuid_location = str(uuid.uuid4())
84
+ new_mac_address = _generate_mac_address()
85
+ new_vmci_id = str(random.randint(-2147483648, 2147483647)) # Random 32-bit integer
86
+
87
+ # Update the content
88
+ updated_content = re.sub(r'displayName = ".*?"', f'displayName = "{target_vm_name}"', original_content)
89
+ updated_content = re.sub(r'uuid.bios = ".*?"', f'uuid.bios = "{new_uuid_bios}"', updated_content)
90
+ updated_content = re.sub(r'uuid.location = ".*?"', f'uuid.location = "{new_uuid_location}"', updated_content)
91
+ updated_content = re.sub(r'ethernet0.generatedAddress = ".*?"',
92
+ f'ethernet0.generatedAddress = "{new_mac_address}"',
93
+ updated_content)
94
+ updated_content = re.sub(r'vmci0.id = ".*?"', f'vmci0.id = "{new_vmci_id}"', updated_content)
95
+
96
+ # Write the updated content back to the file
97
+ with open(vmx_path, 'w') as file:
98
+ file.write(updated_content)
99
+
100
+ logger.info(".vmx file updated successfully.")
101
+
102
+ vmx_file_base_name = os.path.splitext(vmx_file)[0]
103
+
104
+ files_to_rename = ['vmx', 'nvram', 'vmsd', 'vmxf']
105
+
106
+ for ext in files_to_rename:
107
+ original_file = os.path.join(dir_path, f"{vmx_file_base_name}.{ext}")
108
+ target_file = os.path.join(dir_path, f"{target_vm_name}.{ext}")
109
+ os.rename(original_file, target_file)
110
+
111
+ # Update the dir_path to the target vm_name, only replace the last character
112
+ # Split the path into parts up to the last folder
113
+ path_parts = dir_path.rstrip(os.sep).split(os.sep)
114
+ path_parts[-1] = target_vm_name
115
+ target_dir_path = os.sep.join(path_parts)
116
+ os.rename(dir_path, target_dir_path)
117
+
118
+ logger.info("VM files renamed successfully.")
119
+
120
+
121
+ def _install_vm(vm_name, vms_dir, downloaded_file_name, os_type, original_vm_name="Ubuntu"):
122
+ os.makedirs(vms_dir, exist_ok=True)
123
+
124
+ def __download_and_unzip_vm():
125
+ # Download the virtual machine image
126
+ logger.info("Downloading the virtual machine image...")
127
+ downloaded_size = 0
128
+
129
+ if os_type == "Ubuntu":
130
+ if platform.system() == 'Darwin':
131
+ URL = UBUNTU_ARM_URL
132
+ elif platform.machine().lower() in ['amd64', 'x86_64']:
133
+ URL = UBUNTU_X86_URL
134
+ elif os_type == "Windows":
135
+ if platform.machine().lower() in ['amd64', 'x86_64']:
136
+ URL = WINDOWS_X86_URL
137
+ DOWNLOADED_FILE_NAME = URL.split('/')[-1]
138
+ downloaded_file_name = DOWNLOADED_FILE_NAME
139
+
140
+ while True:
141
+ downloaded_file_path = os.path.join(vms_dir, downloaded_file_name)
142
+ headers = {}
143
+ if os.path.exists(downloaded_file_path):
144
+ downloaded_size = os.path.getsize(downloaded_file_path)
145
+ headers["Range"] = f"bytes={downloaded_size}-"
146
+
147
+ with requests.get(URL, headers=headers, stream=True) as response:
148
+ if response.status_code == 416:
149
+ # This means the range was not satisfiable, possibly the file was fully downloaded
150
+ logger.info("Fully downloaded or the file size changed.")
151
+ break
152
+
153
+ response.raise_for_status()
154
+ total_size = int(response.headers.get('content-length', 0))
155
+
156
+ with open(downloaded_file_path, "ab") as file, tqdm(
157
+ desc="Progress",
158
+ total=total_size,
159
+ unit='iB',
160
+ unit_scale=True,
161
+ unit_divisor=1024,
162
+ initial=downloaded_size,
163
+ ascii=True
164
+ ) as progress_bar:
165
+ try:
166
+ for data in response.iter_content(chunk_size=1024):
167
+ size = file.write(data)
168
+ progress_bar.update(size)
169
+ except (requests.exceptions.RequestException, IOError) as e:
170
+ logger.error(f"Download error: {e}")
171
+ sleep(RETRY_INTERVAL)
172
+ logger.error("Retrying...")
173
+ else:
174
+ logger.info("Download succeeds.")
175
+ break # Download completed successfully
176
+
177
+ # Unzip the downloaded file
178
+ logger.info("Unzipping the downloaded file...☕️")
179
+ with zipfile.ZipFile(downloaded_file_path, 'r') as zip_ref:
180
+ zip_ref.extractall(os.path.join(vms_dir, vm_name))
181
+ logger.info("Files have been successfully extracted to the directory: " + str(os.path.join(vms_dir, vm_name)))
182
+
183
+ vm_path = os.path.join(vms_dir, vm_name, vm_name + ".vmx")
184
+
185
+ # Execute the function to download and unzip the VM, and update the vm metadata
186
+ if not os.path.exists(vm_path):
187
+ __download_and_unzip_vm()
188
+ _update_vm(os.path.join(vms_dir, vm_name, original_vm_name + ".vmx"), vm_name)
189
+ else:
190
+ logger.info(f"Virtual machine exists: {vm_path}")
191
+
192
+ # Determine the platform of the host machine and decide the parameter for vmrun
193
+ def get_vmrun_type():
194
+ if platform.system() == 'Windows' or platform.system() == 'Linux':
195
+ return '-T ws'
196
+ elif platform.system() == 'Darwin': # Darwin is the system name for macOS
197
+ return '-T fusion'
198
+ else:
199
+ raise Exception("Unsupported operating system")
200
+
201
+ # Start the virtual machine
202
+ def start_vm(vm_path, max_retries=20):
203
+ command = f'vmrun {get_vmrun_type()} start "{vm_path}" nogui'
204
+ for attempt in range(max_retries):
205
+ result = subprocess.run(command, shell=True, text=True, capture_output=True, encoding="utf-8")
206
+ if result.returncode == 0:
207
+ logger.info("Virtual machine started.")
208
+ return True
209
+ else:
210
+ if "Error" in result.stderr:
211
+ logger.error(f"Attempt {attempt + 1} failed with specific error: {result.stderr}")
212
+ else:
213
+ logger.error(f"Attempt {attempt + 1} failed: {result.stderr}")
214
+
215
+ if attempt == max_retries - 1:
216
+ logger.error("Maximum retry attempts reached, failed to start the virtual machine.")
217
+ return False
218
+
219
+ if not start_vm(vm_path):
220
+ raise ValueError("Error encountered during installation, please rerun the code for retrying.")
221
+
222
+ def get_vm_ip(vm_path, max_retries=20):
223
+ command = f'vmrun {get_vmrun_type()} getGuestIPAddress "{vm_path}" -wait'
224
+ for attempt in range(max_retries):
225
+ result = subprocess.run(command, shell=True, text=True, capture_output=True, encoding="utf-8")
226
+ if result.returncode == 0:
227
+ return result.stdout.strip()
228
+ else:
229
+ if "Error" in result.stderr:
230
+ logger.error(f"Attempt {attempt + 1} failed with specific error: {result.stderr}")
231
+ else:
232
+ logger.error(f"Attempt {attempt + 1} failed: {result.stderr}")
233
+
234
+ if attempt == max_retries - 1:
235
+ logger.error("Maximum retry attempts reached, failed to get the IP of virtual machine.")
236
+ return None
237
+
238
+ vm_ip = get_vm_ip(vm_path)
239
+ if not vm_ip:
240
+ raise ValueError("Error encountered during installation, please rerun the code for retrying.")
241
+
242
+ # Function used to check whether the virtual machine is ready
243
+ def download_screenshot(ip):
244
+ url = f"http://{ip}:5000/screenshot"
245
+ try:
246
+ # max trey times 1, max timeout 1
247
+ response = requests.get(url, timeout=(10, 10))
248
+ if response.status_code == 200:
249
+ return True
250
+ except Exception as e:
251
+ logger.error(f"Error: {e}")
252
+ logger.error(f"Type: {type(e).__name__}")
253
+ logger.error(f"Error detail: {str(e)}")
254
+ sleep(RETRY_INTERVAL)
255
+ return False
256
+
257
+ # Try downloading the screenshot until successful
258
+ while not download_screenshot(vm_ip):
259
+ # Try to get the IP again in case it has changed
260
+ vm_ip = get_vm_ip(vm_path)
261
+ logger.info("Check whether the virtual machine is ready...")
262
+
263
+ logger.info("Virtual machine is ready. Start to make a snapshot on the virtual machine. It would take a while...")
264
+
265
+ def create_vm_snapshot(vm_path, max_retries=20):
266
+ command = f'vmrun {get_vmrun_type()} snapshot "{vm_path}" "init_state"'
267
+ for attempt in range(max_retries):
268
+ result = subprocess.run(command, shell=True, text=True, capture_output=True, encoding="utf-8")
269
+ if result.returncode == 0:
270
+ logger.info("Snapshot created.")
271
+ return True
272
+ else:
273
+ if "Error" in result.stderr:
274
+ logger.error(f"Attempt {attempt + 1} failed with specific error: {result.stderr}")
275
+ else:
276
+ logger.error(f"Attempt {attempt + 1} failed: {result.stderr}")
277
+
278
+ if attempt == max_retries - 1:
279
+ logger.error("Maximum retry attempts reached, failed to create snapshot.")
280
+ return False
281
+
282
+ # Create a snapshot of the virtual machine
283
+ if create_vm_snapshot(vm_path, max_retries=MAX_RETRY_TIMES):
284
+ return vm_path
285
+ else:
286
+ raise ValueError("Error encountered during installation, please rerun the code for retrying.")
287
+
288
+
289
+ class VMwareVMManager(VMManager):
290
+ def __init__(self, registry_path=REGISTRY_PATH):
291
+ self.registry_path = registry_path
292
+ self.lock = FileLock(LOCK_FILE_NAME, timeout=60)
293
+ self.initialize_registry()
294
+
295
+ def initialize_registry(self):
296
+ with self.lock: # Locking during initialization
297
+ if not os.path.exists(self.registry_path):
298
+ with open(self.registry_path, 'w') as file:
299
+ file.write('')
300
+
301
+ def add_vm(self, vm_path, lock_needed=True):
302
+ if lock_needed:
303
+ with self.lock:
304
+ self._add_vm(vm_path)
305
+ else:
306
+ self._add_vm(vm_path)
307
+
308
+ def _add_vm(self, vm_path, region=None):
309
+ assert region in [None, 'local'], "For VMware provider, the region should be neither None or 'local'."
310
+ with self.lock:
311
+ with open(self.registry_path, 'r') as file:
312
+ lines = file.readlines()
313
+ new_lines = lines + [f'{vm_path}|free\n']
314
+ with open(self.registry_path, 'w') as file:
315
+ file.writelines(new_lines)
316
+
317
+ def occupy_vm(self, vm_path, pid, lock_needed=True):
318
+ if lock_needed:
319
+ with self.lock:
320
+ self._occupy_vm(vm_path, pid)
321
+ else:
322
+ self._occupy_vm(vm_path, pid)
323
+
324
+ def _occupy_vm(self, vm_path, pid, region=None):
325
+ assert region in [None, 'local'], "For VMware provider, the region should be neither None or 'local'."
326
+ with self.lock:
327
+ new_lines = []
328
+ with open(self.registry_path, 'r') as file:
329
+ lines = file.readlines()
330
+ for line in lines:
331
+ registered_vm_path, _ = line.strip().split('|')
332
+ if registered_vm_path == vm_path:
333
+ new_lines.append(f'{registered_vm_path}|{pid}\n')
334
+ else:
335
+ new_lines.append(line)
336
+ with open(self.registry_path, 'w') as file:
337
+ file.writelines(new_lines)
338
+
339
+ def delete_vm(self, vm_path, lock_needed=True):
340
+ if lock_needed:
341
+ with self.lock:
342
+ self._delete_vm(vm_path)
343
+ else:
344
+ self._delete_vm(vm_path)
345
+
346
+ def _delete_vm(self, vm_path):
347
+ raise NotImplementedError
348
+
349
+ def check_and_clean(self, vms_dir, lock_needed=True):
350
+ if lock_needed:
351
+ with self.lock:
352
+ self._check_and_clean(vms_dir)
353
+ else:
354
+ self._check_and_clean(vms_dir)
355
+
356
+ def _check_and_clean(self, vms_dir):
357
+ with self.lock: # Lock when cleaning up the registry and vms_dir
358
+ # Check and clean on the running vms, detect the released ones and mark then as 'free'
359
+ active_pids = {p.pid for p in psutil.process_iter()}
360
+ new_lines = []
361
+ vm_paths = []
362
+
363
+ with open(self.registry_path, 'r') as file:
364
+ lines = file.readlines()
365
+ for line in lines:
366
+ vm_path, pid_str = line.strip().split('|')
367
+ if not os.path.exists(vm_path):
368
+ logger.info(f"VM {vm_path} not found, releasing it.")
369
+ new_lines.append(f'{vm_path}|free\n')
370
+ continue
371
+
372
+ vm_paths.append(vm_path)
373
+ if pid_str == "free":
374
+ new_lines.append(line)
375
+ continue
376
+
377
+ if int(pid_str) in active_pids:
378
+ new_lines.append(line)
379
+ else:
380
+ new_lines.append(f'{vm_path}|free\n')
381
+ with open(self.registry_path, 'w') as file:
382
+ file.writelines(new_lines)
383
+
384
+ # Check and clean on the files inside vms_dir, delete the unregistered ones
385
+ os.makedirs(vms_dir, exist_ok=True)
386
+ vm_names = os.listdir(vms_dir)
387
+ for vm_name in vm_names:
388
+ # skip the downloaded .zip file
389
+ if ".zip" in vm_name:
390
+ continue
391
+ # Skip the .DS_Store file on macOS
392
+ if vm_name == ".DS_Store":
393
+ continue
394
+
395
+ flag = True
396
+ for vm_path in vm_paths:
397
+ if vm_name + ".vmx" in vm_path:
398
+ flag = False
399
+ if flag:
400
+ shutil.rmtree(os.path.join(vms_dir, vm_name))
401
+
402
+ def list_free_vms(self, lock_needed=True):
403
+ if lock_needed:
404
+ with self.lock:
405
+ return self._list_free_vms()
406
+ else:
407
+ return self._list_free_vms()
408
+
409
+ def _list_free_vms(self):
410
+ with self.lock: # Lock when reading the registry
411
+ free_vms = []
412
+ with open(self.registry_path, 'r') as file:
413
+ lines = file.readlines()
414
+ for line in lines:
415
+ vm_path, pid_str = line.strip().split('|')
416
+ if pid_str == "free":
417
+ free_vms.append((vm_path, pid_str))
418
+ return free_vms
419
+
420
+ def get_vm_path(self, os_type, region=None, screen_size=(1920, 1080), **kwargs):
421
+ # Note: screen_size parameter is ignored for VMware provider
422
+ # but kept for interface consistency with other providers
423
+ with self.lock:
424
+ if not VMwareVMManager.checked_and_cleaned:
425
+ VMwareVMManager.checked_and_cleaned = True
426
+ self._check_and_clean(vms_dir=VMS_DIR)
427
+
428
+ allocation_needed = False
429
+ with self.lock:
430
+ free_vms_paths = self._list_free_vms()
431
+ if len(free_vms_paths) == 0:
432
+ # No free virtual machine available, generate a new one
433
+ allocation_needed = True
434
+ else:
435
+ # Choose the first free virtual machine
436
+ chosen_vm_path = free_vms_paths[0][0]
437
+ self._occupy_vm(chosen_vm_path, os.getpid())
438
+ return chosen_vm_path
439
+
440
+ if allocation_needed:
441
+ logger.info("No free virtual machine available. Generating a new one, which would take a while...☕")
442
+ new_vm_name = generate_new_vm_name(vms_dir=VMS_DIR, os_type=os_type)
443
+
444
+ original_vm_name = None
445
+ if os_type == "Ubuntu":
446
+ original_vm_name = "Ubuntu"
447
+ elif os_type == "Windows":
448
+ original_vm_name = "Windows 10 x64"
449
+
450
+ new_vm_path = _install_vm(new_vm_name, vms_dir=VMS_DIR,
451
+ downloaded_file_name=DOWNLOADED_FILE_NAME, original_vm_name=original_vm_name, os_type=os_type)
452
+ with self.lock:
453
+ self._add_vm(new_vm_path)
454
+ self._occupy_vm(new_vm_path, os.getpid())
455
+ return new_vm_path
@@ -0,0 +1,105 @@
1
+ import logging
2
+ import os
3
+ import platform
4
+ import subprocess
5
+ import time
6
+
7
+ from desktop_env.providers.base import Provider
8
+
9
+ logger = logging.getLogger("desktopenv.providers.vmware.VMwareProvider")
10
+ logger.setLevel(logging.INFO)
11
+
12
+ WAIT_TIME = 3
13
+
14
+
15
+ def get_vmrun_type(return_list=False):
16
+ if platform.system() == 'Windows' or platform.system() == 'Linux':
17
+ if return_list:
18
+ return ['-T', 'ws']
19
+ else:
20
+ return '-T ws'
21
+ elif platform.system() == 'Darwin': # Darwin is the system name for macOS
22
+ if return_list:
23
+ return ['-T', 'fusion']
24
+ else:
25
+ return '-T fusion'
26
+ else:
27
+ raise Exception("Unsupported operating system")
28
+
29
+
30
+ class VMwareProvider(Provider):
31
+ @staticmethod
32
+ def _execute_command(command: list, return_output=False):
33
+ process = subprocess.Popen(
34
+ command,
35
+ stdout=subprocess.PIPE,
36
+ stderr=subprocess.PIPE,
37
+ text=True,
38
+ encoding="utf-8"
39
+ )
40
+
41
+ if return_output:
42
+ output = process.communicate()[0].strip()
43
+ return output
44
+ else:
45
+ return None
46
+
47
+ def start_emulator(self, path_to_vm: str, headless: bool, os_type: str):
48
+ print("Starting VMware VM...")
49
+ logger.info("Starting VMware VM...")
50
+
51
+ while True:
52
+ try:
53
+ output = subprocess.check_output(f"vmrun {get_vmrun_type()} list", shell=True, stderr=subprocess.STDOUT)
54
+ output = output.decode()
55
+ output = output.splitlines()
56
+ normalized_path_to_vm = os.path.abspath(os.path.normpath(path_to_vm))
57
+
58
+ if any(os.path.abspath(os.path.normpath(line)) == normalized_path_to_vm for line in output):
59
+ logger.info("VM is running.")
60
+ break
61
+ else:
62
+ logger.info("Starting VM...")
63
+ _command = ["vmrun"] + get_vmrun_type(return_list=True) + ["start", path_to_vm]
64
+ if headless:
65
+ _command.append("nogui")
66
+ VMwareProvider._execute_command(_command)
67
+ time.sleep(WAIT_TIME)
68
+
69
+ except subprocess.CalledProcessError as e:
70
+ logger.error(f"Error executing command: {e.output.decode().strip()}")
71
+
72
+ def get_ip_address(self, path_to_vm: str) -> str:
73
+ logger.info("Getting VMware VM IP address...")
74
+ while True:
75
+ try:
76
+ output = VMwareProvider._execute_command(
77
+ ["vmrun"] + get_vmrun_type(return_list=True) + ["getGuestIPAddress", path_to_vm, "-wait"],
78
+ return_output=True
79
+ )
80
+ logger.info(f"VMware VM IP address: {output}")
81
+ return output
82
+ except Exception as e:
83
+ logger.error(e)
84
+ time.sleep(WAIT_TIME)
85
+ logger.info("Retrying to get VMware VM IP address...")
86
+
87
+ def save_state(self, path_to_vm: str, snapshot_name: str):
88
+ logger.info("Saving VMware VM state...")
89
+ VMwareProvider._execute_command(
90
+ ["vmrun"] + get_vmrun_type(return_list=True) + ["snapshot", path_to_vm, snapshot_name])
91
+ time.sleep(WAIT_TIME) # Wait for the VM to save
92
+
93
+ def revert_to_snapshot(self, path_to_vm: str, snapshot_name: str):
94
+ logger.info(f"Reverting VMware VM to snapshot: {snapshot_name}...")
95
+ VMwareProvider._execute_command(
96
+ ["vmrun"] + get_vmrun_type(return_list=True) + ["revertToSnapshot", path_to_vm, snapshot_name])
97
+ time.sleep(WAIT_TIME) # Wait for the VM to revert
98
+ return path_to_vm
99
+
100
+ def stop_emulator(self, path_to_vm: str, region=None, *args, **kwargs):
101
+ # Note: region parameter is ignored for VMware provider
102
+ # but kept for interface consistency with other providers
103
+ logger.info("Stopping VMware VM...")
104
+ VMwareProvider._execute_command(["vmrun"] + get_vmrun_type(return_list=True) + ["stop", path_to_vm])
105
+ time.sleep(WAIT_TIME) # Wait for the VM to stop
gui_agents/__init__.py ADDED
File without changes