55import importlib
66import inspect
77import json
8- import sqlite3
9-
108import luadata
119import os
1210import pkgutil
1311import psutil
1412import shutil
13+ import sqlite3
1514import sys
1615import tempfile
1716import time
2019if sys .platform == 'win32' :
2120 import win32con
2221 import win32gui
22+ import win32process
2323
2424from collections import OrderedDict
2525from contextlib import suppress
@@ -157,6 +157,7 @@ class ServerImpl(Server):
157157 bot : DCSServerBot | None = field (compare = False , init = False )
158158 event_handler : MissionFileSystemEventHandler = field (compare = False , default = None )
159159 observer : ObserverType = field (compare = False , default = None )
160+ process : psutil .Process | None = field (default = None , compare = False )
160161
161162 @override
162163 def __post_init__ (self ):
@@ -493,7 +494,7 @@ async def send_to_dcs(self, message: dict) -> None:
493494
494495 @override
495496 async def rename (self , new_name : str , update_settings : bool = False ) -> None :
496- def update_config (old_name , new_name : str , update_settings : bool = False ):
497+ def update_config (old_name : str | None , new_name : str , update_settings : bool = False ):
497498 # update servers.yaml
498499 filename = os .path .join (self .node .config_dir , 'servers.yaml' )
499500 if os .path .exists (filename ):
@@ -510,7 +511,7 @@ def update_config(old_name, new_name: str, update_settings: bool = False):
510511 if update_settings :
511512 self .settings ['name' ] = new_name
512513
513- async def update_database (old_name : str , new_name : str ):
514+ async def update_database (old_name : str | None , new_name : str ):
514515 # rename the server in the database
515516 async with self .apool .connection () as conn :
516517 await conn .execute ("UPDATE servers SET server_name = %s WHERE server_name = %s" ,
@@ -547,7 +548,8 @@ async def update_cluster(new_name: str):
547548 self .name = new_name
548549 except Exception :
549550 # rollback config
550- update_config (new_name , old_name , update_settings )
551+ if old_name :
552+ update_config (new_name , old_name , update_settings )
551553 raise
552554 except Exception :
553555 self .log .exception (f"Error during renaming of server { old_name } to { new_name } : " , exc_info = True )
@@ -619,7 +621,7 @@ def do_startup(self):
619621 instance = self .instance .name
620622 )
621623 if 'priority' in self .locals :
622- self .set_priority (self .locals . get ( 'priority' ) )
624+ self .set_priority (self .locals [ 'priority' ] )
623625 self .log .info (f" => DCS server starting up with PID { self .process .pid } " )
624626 except Exception :
625627 self .log .error (f" => Error while trying to launch DCS!" , exc_info = True )
@@ -672,25 +674,63 @@ async def prepare_extensions(self):
672674 except Exception :
673675 self .log .error (f" => Unknown error during { ext .name } .prepare() - skipped." , exc_info = True )
674676
675- @staticmethod
676- def _window_enumeration_handler (hwnd , top_windows ):
677- top_windows .append ((hwnd , win32gui .GetWindowText (hwnd )))
678-
679677 def _minimize (self ):
680- top_windows = []
681- win32gui .EnumWindows (self ._window_enumeration_handler , top_windows )
678+ """
679+ Minimize the main window of the process represented by
680+ self.process (a psutil.Process instance).
681+
682+ If no window is found or the window disappears before the
683+ message is sent, the call is silently ignored.
684+ """
685+ target_pid = self .process .pid
686+
687+ # ------------------------------------------------------------------
688+ # 1. Gather all top‑level windows that belong to the target PID
689+ # ------------------------------------------------------------------
690+ candidate_windows = []
691+
692+ def enum_handler (hwnd , _ ):
693+ # Skip invisible or child windows – we only care about
694+ # top‑level windows that belong to the process.
695+ if not win32gui .IsWindowVisible (hwnd ):
696+ return True
697+
698+ try :
699+ _ , win_pid = win32process .GetWindowThreadProcessId (hwnd )
700+ except Exception :
701+ # Some exotic windows can raise an exception when queried.
702+ return True
703+
704+ if win_pid == target_pid :
705+ title = win32gui .GetWindowText (hwnd )
706+ candidate_windows .append ((hwnd , title ))
707+
708+ return True # keep enumerating
682709
683- # Fetch the window name of the process
684- window_name = self .instance .name
710+ win32gui .EnumWindows (enum_handler , None )
685711
686- for hwnd , title in top_windows :
687- if window_name .lower () in title .lower ():
688- # non-blocking call
712+ if not candidate_windows :
713+ self .log .debug ("No visible window found for PID %s" , target_pid )
714+ return
715+
716+ # ------------------------------------------------------------------
717+ # 2. Pick the “main” window – usually the first one we found.
718+ # ------------------------------------------------------------------
719+ hwnd , title = candidate_windows [0 ]
720+
721+ # ------------------------------------------------------------------
722+ # 3. Post the minimize command – only if the handle is still valid
723+ # ------------------------------------------------------------------
724+ try :
725+ if win32gui .IsWindow (hwnd ):
689726 win32gui .PostMessage (hwnd ,
690727 win32con .WM_SYSCOMMAND ,
691728 win32con .SC_MINIMIZE ,
692729 0 )
693- break
730+ else :
731+ self .log .debug ("Handle %s no longer refers to a window" , hwnd )
732+ except win32gui .error as exc :
733+ return
694734
695735 def set_priority (self , priority : str ):
696736 if priority == 'below_normal' :
@@ -987,7 +1027,12 @@ async def render_extensions(self) -> list[dict]:
9871027
9881028 @override
9891029 async def restart (self , modify_mission : bool | None = True , use_orig : bool | None = True ) -> None :
990- await self .loadMission (self ._get_current_mission_file (), modify_mission = modify_mission , use_orig = use_orig )
1030+ current_mission = self ._get_current_mission_file ()
1031+ if current_mission :
1032+ await self .loadMission (current_mission , modify_mission = modify_mission , use_orig = use_orig )
1033+ else :
1034+ await self .stop ()
1035+ await self .start ()
9911036
9921037 @override
9931038 async def getStartIndex (self ) -> int :
@@ -1288,7 +1333,7 @@ async def enable_extension(self, name: str, config: dict | None = None) -> bool:
12881333 if not ext :
12891334 ext = self .load_extension (name , config )
12901335 self .extensions [name ] = ext
1291- if await ext .enable ():
1336+ if ext and await ext .enable ():
12921337 await self .config_extension (name , (config or {}) | {"enabled" : True })
12931338 return True
12941339 return False
0 commit comments