259 lines
12 KiB
Python
259 lines
12 KiB
Python
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())
|