import os import sys import time import logging # --- pywinauto 관련 --- from pywinauto import Application, findwindows, timings from pywinauto.controls.hwndwrapper import HwndWrapper # --- Selenium 관련 --- from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options # --- PySide6 관련 --- from PySide6.QtCore import Qt, QTimer, QEvent from PySide6.QtWidgets import ( QApplication, QMainWindow, QFrame, QSplitter, QVBoxLayout, QPushButton ) # --- Win32 API (임베딩에 사용) --- import win32gui import win32con # ============================================================ # 간단한 Logger 클래스 # ============================================================ class Logger: def __init__(self, log_file="app.log", logger_name="App_Logger", level=logging.DEBUG): self.logger = logging.getLogger(logger_name) self.logger.setLevel(level) if not self.logger.handlers: handler = logging.FileHandler(log_file, encoding="utf-8") formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) self.logger.addHandler(handler) def log(self, msg, level=logging.INFO, exc_info=False): self.logger.log(level, msg, exc_info=exc_info) # ============================================================ # WhaleController 클래스 # ============================================================ class WhaleController: def __init__(self): self.logger = Logger(log_file="app.log", logger_name="App_Logger", level=logging.DEBUG) self.logger.log("로그기록이 설정되었습니다.", level=logging.INFO) self.driver = None self.browser_pid = None self.whale_window = None def find_whale(self): """pywinauto를 사용하여 웨일 브라우저 창을 찾아 제어""" try: if not self.browser_pid: self.logger.log("브라우저 PID를 찾을 수 없습니다. 먼저 Selenium으로 실행해야 합니다.", level=logging.WARNING) return # 최대 10초 동안 'whale'이 포함된 창이 나타나기를 기다림 timings.wait_until(10, 0.5, lambda: any("whale" in window.name.lower() for window in findwindows.find_elements())) windows = findwindows.find_elements() for window in windows: if "whale" in window.name.lower() or window.process_id == self.browser_pid: self.logger.log(f"찾은 창: {window.name}, PID: {window.process_id}", level=logging.DEBUG) if window.name == 'whale://new-tab-page-third-party/ - Whale' or "whale" in window.name.lower(): self.logger.log("웨일 브라우저를 찾았음: " + window.name, level=logging.INFO) whale_app = Application(backend="uia").connect(process=window.process_id) self.whale_window = whale_app.top_window() # 초기 위치 및 크기 조정 (나중에 GUI 내 임베딩 시 재조정됨) hwnd_wrapper = HwndWrapper(self.whale_window.handle) hwnd_wrapper.move_window(x=1, y=1, width=1280, height=720) self.whale_window.set_focus() break except Exception as e: self.logger.log(f"웨일 제어 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) def get_base_dir(self): """ 실행 환경에 따라 base_dir 설정. cx_Freeze 패키징 여부에 따라 __file__ 또는 sys.executable 기준. """ if getattr(sys, 'frozen', False): base_dir = os.path.dirname(sys.executable) internal_dir = os.path.join(base_dir, '_internal') if os.path.exists(internal_dir): return internal_dir else: base_dir = os.path.dirname(os.path.abspath(__file__)) return base_dir def start_whale_Browser(self): """웨일 브라우저 실행 및 Selenium 제어""" try: base_path = self.get_base_dir() # 아래 경로들은 실제 파일 위치에 맞게 수정하세요. whale_exe_path = os.path.join(base_path, "browsers", "whale", "whale.exe") user_data_dir = os.path.join(base_path, "browsers", "whale", "user_data") cache_dir = os.path.join(base_path, "browsers", "whale", "cache") extension_path = os.path.join(base_path, "browsers", "whale", "extensions", "gadfmnjdnhkncfcibhfleoojcdimdcbd", "1.1.11_0") chromedriver_path = os.path.join(base_path, "browsers", "chromedriver_128.0.6613.137.exe") chrome_service = Service(chromedriver_path) chrome_options = Options() chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--remote-debugging-port=9222") chrome_options.add_argument(f"--user-data-dir={user_data_dir}") chrome_options.add_argument(f"--disk-cache-dir={cache_dir}") chrome_options.add_argument(f"--load-extension={extension_path}") chrome_options.binary_location = whale_exe_path chrome_options.add_argument( "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/130.0.0.0 Whale/4.29.282.14 Safari/537.36" ) self.driver = webdriver.Chrome(service=chrome_service, options=chrome_options) time.sleep(0.2) self.browser_pid = self.driver.service.process.pid self.logger.log(f"웨일 브라우저 PID: {self.browser_pid}", level=logging.INFO) self.find_whale() except Exception as e: self.logger.log(f"웨일 브라우저 시작 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) def close_whale_br(self): """브라우저 종료""" try: self.logger.log("브라우저를 종료합니다.", level=logging.DEBUG) if self.driver: self.driver.quit() else: self.logger.log("브라우저 객체를 찾을 수 없습니다.", level=logging.WARNING) except Exception as e: self.logger.log(f"브라우저 종료 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) # ============================================================ # 메인 GUI 프로그램 (PySide6) # ============================================================ class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("커스텀 도구 포함 웹GUI 프로그램") self.resize(1400, 800) # QSplitter로 브라우저 임베딩 영역과 도구 패널 분리 splitter = QSplitter(Qt.Horizontal, self) self.setCentralWidget(splitter) # [1] 브라우저 임베딩 영역 self.browser_container = QFrame() self.browser_container.setStyleSheet("background-color: #ffffff;") splitter.addWidget(self.browser_container) # [2] 커스텀 도구 패널 self.tools_panel = QFrame() self.tools_panel.setMinimumWidth(300) splitter.addWidget(self.tools_panel) tools_layout = QVBoxLayout(self.tools_panel) self.btn_close = QPushButton("브라우저 종료") self.btn_refresh = QPushButton("브라우저 새로고침") self.btn_custom = QPushButton("커스텀 도구 실행") tools_layout.addWidget(self.btn_close) tools_layout.addWidget(self.btn_refresh) tools_layout.addWidget(self.btn_custom) tools_layout.addStretch() # 버튼 이벤트 연결 self.btn_close.clicked.connect(self.close_browser) self.btn_refresh.clicked.connect(self.refresh_browser) self.btn_custom.clicked.connect(self.custom_tool_action) # WhaleController 인스턴스 생성 self.whale_controller = WhaleController() self.embedded_browser_hwnd = None # 브라우저 컨테이너에 이벤트 필터 설치: Resize 이벤트 발생 시 임베딩된 브라우저 창 크기를 조정 self.browser_container.installEventFilter(self) QTimer.singleShot(500, self.start_whale_browser) def eventFilter(self, obj, event): if obj == self.browser_container and event.type() == QEvent.Resize: self.resize_embedded_browser() return super().eventFilter(obj, event) def start_whale_browser(self): """웨일 브라우저 실행 후 임베딩""" self.whale_controller.start_whale_Browser() # 약간의 지연 후 embed_browser() 호출하여 웨일 창을 찾아 임베딩 QTimer.singleShot(1000, self.embed_browser) def embed_browser(self): """pywinauto로 찾은 웨일 브라우저 창을 컨테이너에 임베딩""" try: if self.whale_controller.whale_window is None: print("웨일 브라우저 창을 찾지 못했습니다.") return browser_hwnd = self.whale_controller.whale_window.handle self.embedded_browser_hwnd = browser_hwnd # 컨테이너의 윈도우 핸들을 부모로 지정 container_hwnd = int(self.browser_container.winId()) win32gui.SetParent(browser_hwnd, container_hwnd) # 창 스타일 수정 (제목표시줄, 테두리 등 제거) style = win32gui.GetWindowLong(browser_hwnd, win32con.GWL_STYLE) style = style & ~(win32con.WS_CAPTION | win32con.WS_THICKFRAME | win32con.WS_MINIMIZE | win32con.WS_MAXIMIZE | win32con.WS_SYSMENU) win32gui.SetWindowLong(browser_hwnd, win32con.GWL_STYLE, style) self.resize_embedded_browser() except Exception as e: print("브라우저 임베딩 중 오류:", e) def resize_embedded_browser(self): """ 브라우저 컨테이너의 현재 크기를 기준으로 임베딩된 브라우저 창을 컨테이너 크기의 1.245배 크기로 조정합니다. 단, 왼쪽 위 꼭지점은 항상 (0,0)에 고정되고, 오른쪽과 아랫쪽만 변경됩니다. """ if self.embedded_browser_hwnd: container_size = self.browser_container.size() container_width = container_size.width() container_height = container_size.height() new_width = int(container_width * 1.245) new_height = int(container_height * 1.245) # 왼쪽 위 꼭지점은 항상 (0,0) win32gui.MoveWindow(self.embedded_browser_hwnd, 0, 0, new_width, new_height, True) def close_browser(self): """커스텀 도구: 브라우저 종료""" self.whale_controller.close_whale_br() def refresh_browser(self): """커스텀 도구: 브라우저 새로고침""" if self.whale_controller.driver: self.whale_controller.driver.refresh() def custom_tool_action(self): """커스텀 도구: 추가 자동화 작업 실행 (예시)""" print("커스텀 도구 작업이 실행되었습니다.") def resizeEvent(self, event): """메인 윈도우 크기가 변경될 경우에도 임베딩된 브라우저 창을 업데이트""" super().resizeEvent(event) self.resize_embedded_browser() if __name__ == "__main__": app = QApplication(sys.argv) main_win = MainWindow() main_win.show() sys.exit(app.exec())