Esempio n. 1
0
class KeyboardClickTrigger(BaseTrigger):
    controller = pynput.keyboard.Controller()
    TITLE = '單擊鍵盤'
    INFORMATION = ('點擊設定的按鍵')
    DIC_DEFAULT = Dict({
        **BaseTrigger.DIC_DEFAULT, 'other': {
            'keys': {
                'class_name': 'HotKeyEdit',
                'name': '按鍵設定',
                'single_mode': True,
                'fixed': [],
                'variable': '0'
            },
        }
    })

    def activate(self, logger, *args, **kwargs):
        try:
            logger.info(f'【{self.TITLE}】')
            logger.info(f'點擊{self.keys.value_widget.PRESSED_KEY_STR}')
            for key in self.keys.value_widget._PRESSED_KEY:
                self.controller.press(key)
                self.controller.release(key)
            logger.info('執行成功。')
        except:
            logger.info('執行失敗。')
            logger.critical('\n' + traceback.format_exc())
Esempio n. 2
0
class MouseMoveTrigger(BaseTrigger):
    controller = pynput.mouse.Controller()
    TITLE = '滑鼠移動'
    INFORMATION = '將滑鼠移動至指定位置'
    DIC_DEFAULT = Dict({
        **BaseTrigger.DIC_DEFAULT, 'other': {
            'pos': {
                'class_name': 'PositionBox',
                'name': '座標',
                'source': 0,
                'fixed': [0, 0],
                'variable': '0'
            },
        }
    })

    def activate(self, logger, *args, **kwargs):
        try:
            sources = super().activate(*args, **kwargs)
            pyautogui.moveTo(*sources.pos)
            logger.info(f'【{self.TITLE}】')
            logger.info(f'將滑鼠移動至{sources.pos}位置')
            logger.info('執行成功。')
        except:
            logger.info('執行失敗。')
            logger.critical('\n' + traceback.format_exc())
Esempio n. 3
0
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.setupUi(self)
        self.showMaximized()

        self.dic_scripts = Dict()

        # 選單 - 檔案
        self.action_add_category.triggered.connect(lambda e: self.create_category(None))
        self.action_add_script.triggered.connect(lambda e: self.create_script(None))
        self.action_save.triggered.connect(self.save_script)
        self.action_close.triggered.connect(self.closeEvent)
        # 選單 - 檢視
        self.action_file_explorer.triggered.connect(self.switch_file_explorer)
        self.action_script_explorer.triggered.connect(self.switch_script_explorer)
        # 選單 - 操作
        self.action_start_all.triggered.connect(self.start_all)
        self.action_stop_all.triggered.connect(self.stop_all)

        # 畫面修改功能
        self.tree_files.itemDoubleClicked.connect(lambda item: item.script.open_editor())
        self.tree_files.itemChanged.connect(lambda item: item.script.rename(item.text(0)))
        self.main_script.currentChanged.connect(self.detect_savable)

        self.tree_scripts.doubleClicked.connect(
            lambda: self.tree_scripts.currentItem().parent() and
                    self.main_script.currentWidget() and 
                    self.main_script.currentWidget().add_trigger(
                        class_name=self.tree_scripts.currentItem().whatsThis(0)
                    ))

        self.load_list()
Esempio n. 4
0
 def run_script(self):
     kwargs = Dict()
     logger = self.get_logger()
     for row in range(self.editor.lst_trigger.rowCount()):
         manager = self.editor.lst_page.widget(row).manager
         kwargs[str(row)] = manager.activate(logger, **kwargs)
         logger.info('=============================')
Esempio n. 5
0
class FileLoadTrigger(BaseTrigger):
    TITLE = '讀取檔案'
    NEED_SOURCE = True
    INFORMATION = (
        '檔案類型:  選擇圖檔,返回 PIL.Image 類型\n'
        '       選擇文字檔,返回 str 類型\n\n'
        '檔案路徑:  要讀取的目標檔案路徑\n\n'
    )
    DIC_DEFAULT = Dict({
        **BaseTrigger.DIC_DEFAULT,
        'other': {
            'file_type': {
                'class_name': 'ComboBox', 'name': '檔案類型',
                'choices': ['圖檔', '文字檔'], 'fixed': 0, 'variable': '0'},
            'file_path': {
                'class_name': 'FileEdit', 'name': '檔案路徑',
                'method': 'getOpenFileName', 'types': 'All files(*)',
                'source': 0, 'fixed': 'data', 'variable': '0',
            }
        }
    })

    def activate(self, logger, *args, **kwargs):
        try:
            sources = super().activate(*args, **kwargs)
            logger.info(f'【{self.TITLE}】')
            logger.info(f'檔案類型為:{self.DIC_DEFAULT.other.file_type.choices[sources.file_type]}')
            logger.info(f'檔案路徑為:{sources.file_path}')
            logger.info(f'執行成功。')
            if sources.file_type == 0:
                return Image.open(sources.file_path)
            return sources.file_path.read_text(encoding='utf8')
        except:
            logger.info(f'執行失敗。')
            logger.critical('\n' + traceback.format_exc())
Esempio n. 6
0
 def read_data(self):
     if self.path.is_dir():
         return
     if not self.path.read_text(encoding='utf8'):
         self.init_data()
         return
     # 讀取資料
     self.data = Dict.load_json(self.path, encoding='utf8')
Esempio n. 7
0
 def data(self):
     data = Dict({
         'class_name': self.__class__.__name__,
         'other': {
             key: {
                 k: getattr(self, key).current.get(k, v)
                 for k, v in value.items()
             }
             for key, value in self.DIC_DEFAULT.other.items()
         }
     })
     return data
Esempio n. 8
0
class MouseClickTrigger(BaseTrigger):
    controller = pynput.mouse.Controller()
    TITLE = '點擊滑鼠'
    INFORMATION = ('滑鼠按鈕:  選擇左鍵,點擊左鍵\n'
                   '       選擇中鍵,點擊中鍵\n'
                   '       選擇右鍵,點擊右鍵\n\n'
                   '點擊次數:  選擇單擊,點擊一次\n'
                   '       選擇雙擊,點擊兩次')
    DIC_DEFAULT = Dict({
        **BaseTrigger.DIC_DEFAULT, 'other': {
            'button': {
                'class_name': 'ComboBox',
                'name': '滑鼠按鈕',
                'choices': ['左鍵', '中鍵', '右鍵'],
                'fixed': 0,
                'variable': '0'
            },
            'times': {
                'class_name': 'ComboBox',
                'name': '點擊次數',
                'choices': ['單擊', '雙擊'],
                'fixed': 0,
                'variable': '0'
            },
        }
    })

    def activate(self, logger, *args, **kwargs):
        try:
            sources = super().activate(*args, **kwargs)
            dic_buttons = dict(
                enumerate([
                    pynput.mouse.Button.left,
                    pynput.mouse.Button.middle,
                    pynput.mouse.Button.right,
                ]))
            button = dic_buttons[sources.button]
            count = sources.times + 1
            self.controller.click(button, count)

            logger.info(f'【{self.TITLE}】')
            logger.info(
                f'點擊{self.DIC_DEFAULT.other.button.choices[sources.button]}'
                f'{count}次')
            logger.info('執行成功。')
        except:
            logger.info('執行失敗。')
            logger.critical('\n' + traceback.format_exc())
Esempio n. 9
0
class PrintScreenTrigger(BaseTrigger):
    TITLE = '螢幕截圖'
    INFORMATION = (
        '來源(str): 無\n\n'
    )
    DIC_DEFAULT = Dict({
        **BaseTrigger.DIC_DEFAULT,
        'other': {
        }
    })

    def activate(self, logger, *args, **kwargs):
        try:
            img = ImageGrab.grab(all_screens=1)
            logger.info(f'【{self.TITLE}】,執行成功。')
            return img
        except:
            logger.info(f'【{self.TITLE}】,執行失敗。')
            logger.critical('\n' + traceback.format_exc())
Esempio n. 10
0
class CopyTrigger(BaseTrigger):
    controller = pynput.keyboard.Controller()
    TITLE = '剪貼簿複製'
    INFORMATION = ''
    DIC_DEFAULT = Dict({**BaseTrigger.DIC_DEFAULT, 'other': {}})

    def activate(self, logger, *args, **kwargs):
        try:
            logger.info(f'【{self.TITLE}】')
            keys = [
                pynput.keyboard.Key.ctrl,
                pynput.keyboard.KeyCode.from_char('c')
            ]
            for key in keys:
                self.controller.press(key)
            for key in keys:
                self.controller.release(key)
            logger.info(f'執行成功。')
        except:
            logger.info(f'執行失敗。')
            logger.critical('\n' + traceback.format_exc())
Esempio n. 11
0
class MouseScrollTrigger(BaseTrigger):
    controller = pynput.mouse.Controller()
    TITLE = '滾動滑鼠'
    INFORMATION = ('方向:  選擇向上滾動,滾輪向上滾動\n'
                   '     選擇向下滾動,滾輪向下滾動\n\n'
                   '滾動次數:  輸入要滾動的次數')
    DIC_DEFAULT = Dict({
        **BaseTrigger.DIC_DEFAULT, 'other': {
            'forward': {
                'class_name': 'ComboBox',
                'name': '方向',
                'choices': ['向上滾動', '向下滾動'],
                'fixed': 0,
                'variable': '0'
            },
            'step': {
                'class_name': 'SpinBox',
                'name': '滾動次數',
                'fixed': 0,
                'variable': '0'
            },
        }
    })

    def activate(self, logger, *args, **kwargs):
        try:
            sources = super().activate(*args, **kwargs)
            logger.info(f'【{self.TITLE}】')
            logger.info(
                f'{self.DIC_DEFAULT.other.forward.choices[sources.forward]}'
                f'{sources.step}次')
            if sources.forward == 0:
                forward = 1
            else:
                forward = -1
            self.controller.scroll(0, forward * sources.step)
            logger.info('執行成功。')
        except:
            logger.info('執行失敗。')
            logger.critical('\n' + traceback.format_exc())
Esempio n. 12
0
    def data(self) -> Dict:
        if self.path.is_dir():
            return self._data

        if self.rb_once.isChecked():
            category = 'once'
        if self.rb_while.isChecked():
            category = 'while'

        return Dict({
            'BASIC': {
                'activate': bool(self.ccb_activate.currentIndex()),
                'start_hotkey': self.le_start_hotkey.PRESSED_KEY_VK,
                'stop_hotkey': self.le_stop_hotkey.PRESSED_KEY_VK,
                'descript': self.te_descript.toPlainText(),
                'category': category
            },
            'SCRIPT': {
                'content': [
                    self.lst_page.widget(row).manager.data
                    for row in range(self.lst_trigger.rowCount())
                ]
            },
        })
Esempio n. 13
0
class BaseTrigger:
    TITLE = ''
    INFORMATION = ''
    NEED_SOURCE = False
    DIC_DEFAULT = Dict({
        'other': {},
    })
    _row = 0
    class Trigger(QtWidgets.QWidget, Ui_Form):
        def __init__(self, manager, data, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.setupUi(self)
            self.manager = manager
            self.lbl_title.setText(manager.TITLE)
            self.lbl_information.setText(manager.INFORMATION)
            for i, (key, value) in enumerate(data.other.items()):
                name_widget = QtWidgets.QLabel(f'{value.name}:')
                value_widget = ArgsWidget(value)
                value_widget.le_source_variable.right = self
                self.formLayout_2.setWidget(i, QtWidgets.QFormLayout.LabelRole, name_widget)
                self.formLayout_2.setWidget(i, QtWidgets.QFormLayout.FieldRole, value_widget)
                value_widget.sig_current_changed.connect(self.manager.editor.check_saved)
                setattr(self, key, value_widget)
                setattr(self.manager, key, value_widget)

        @property
        def row(self):
            return self.manager.row
        @row.setter
        def row(self, row):
            self.manager.row = row

        @property
        def data(self):
            return self.manager.data

    @property
    def row(self):
        return self._row
    @row.setter
    def row(self, row):
        self._row = row
        self.right.lbl_index.setText(str(row))

    @property
    def data(self):
        data = Dict({
            'class_name': self.__class__.__name__,
            'other': {
                key: {
                    k: getattr(self, key).current.get(k, v)
                    for k, v in value.items()
                }
                for key, value in self.DIC_DEFAULT.other.items()
            }
        })
        return data
    @data.setter
    def data(self, data):
        for key, value in data.other.items():
            value_widget = getattr(self.right, key)
            value_widget.current = value

    def __init__(self, editor, data=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.editor = editor
        # 腳本清單
        self.header_item = QtWidgets.QTableWidgetItem()
        self.handle_item = QtWidgets.QWidget()
        self.name_item = QtWidgets.QTableWidgetItem(self.TITLE)
        # 腳本清單-操作
        h = QtWidgets.QHBoxLayout(self.handle_item)
        self.pb_remove = QtWidgets.QPushButton('X')
        self.pb_move_up = QtWidgets.QPushButton('↑')
        self.pb_move_down = QtWidgets.QPushButton('↓')
        self.pb_remove.clicked.connect(lambda: self.editor.remove_trigger(self))
        self.pb_move_up.clicked.connect(lambda: self.editor.move_up_trigger(self))
        self.pb_move_down.clicked.connect(lambda: self.editor.move_down_trigger(self))
        h.addWidget(self.pb_remove)
        h.addWidget(self.pb_move_up)
        h.addWidget(self.pb_move_down)

        # 腳本詳細內容
        data = data or self.DIC_DEFAULT
        self.right = self.Trigger(self, data)
        self.data = data

    def activate(self, *args, **kwargs):
        return Dict({
            key: getattr(self, key).get_source(kwargs)
            for key in self.DIC_DEFAULT.other
        })
Esempio n. 14
0
 def current(self):
     return Dict({
         'source': self.ccb_source.currentIndex(),
         'fixed': self.value_widget.current,
         'variable': self.le_source_variable.text()
     })
Esempio n. 15
0
 def activate(self, *args, **kwargs):
     return Dict({
         key: getattr(self, key).get_source(kwargs)
         for key in self.DIC_DEFAULT.other
     })
Esempio n. 16
0
class FileSaveTrigger(BaseTrigger):
    TITLE = '儲存檔案'
    NEED_SOURCE = True
    INFORMATION = (
        '存檔內容:  要儲存的檔案內容\n\n'
        '存檔路徑:  要儲存的目標檔案路徑\n\n'
        '檔案類型:  選擇圖檔,返回 PIL.Image 類型\n'
        '       選擇文字檔,返回 str 類型\n\n'
        '複寫: 選擇總是覆蓋,每次執行將覆蓋舊檔\n'
        '    選擇忽略,若檔案存在就不會重覆執行存檔動作\n'
        '    選擇另存新檔,將在檔案目錄建立新檔儲存\n'
    )
    DIC_DEFAULT = Dict({
        **BaseTrigger.DIC_DEFAULT,
        'other': {
            'write_content': {
                'class_name': 'TextEdit', 'name': '存檔內容',
                'source': 1, 'fixed': '', 'variable': '0',
            },
            'file_path': {
                'class_name': 'FileEdit', 'name': '檔案路徑',
                'method': 'getSaveFileName', 'types': 'All files(*)',
                'source': 0, 'fixed': 'data', 'variable': '0',
            },
            'file_type': {
                'class_name': 'ComboBox', 'name': '檔案類型',
                'choices': ['圖檔', '文字檔'], 'fixed': 0, 'variable': '0'},
            'over_write': {
                'class_name': 'ComboBox', 'name': '覆寫',
                'choices': ['總是覆蓋', '忽略', '另存新檔'], 'fixed': 0, 'variable': '0'},
        }
    })

    def activate(self, logger, *args, **kwargs):
        try:
            logger.info(f'【{self.TITLE}】')
            sources = super().activate(*args, **kwargs)
            file_path = Path(sources.file_path)
            logger.info(f'檔案類型為:{self.DIC_DEFAULT.other.file_type.choices[sources.file_type]}')
            logger.info(f'複寫設定為:{self.DIC_DEFAULT.other.over_write.choices[sources.over_write]}')
            if not file_path.exists():
                file_path = sources.file_path
                logger.info(f'檔案路徑為:{file_path}')
            elif sources.over_write == 0:
                file_path = sources.file_path
                logger.info(f'檔案路徑為:{file_path}')
            elif sources.over_write == 1:
                logger.info(f'檔案已存在,忽略此步驟')
                return True
            elif sources.over_write == 2:
                cnt = len([f for f in file_path.parent.iterdir() if f.stem in str(f)])
                file_path = file_path.parent / f'{file_path.stem} ({cnt}).{file_path.suffix}'
                logger.info(f'檔案路徑為:{file_path}')

            if sources.file_type == 0:
                if isinstance(sources.write_content, Image.Image):
                    sources.write_content.save(file_path)
                    logger.info(f'執行成功。')
                    return True
                else:
                    logger.info(f'欲存檔內容型別錯誤:{sources.write_content}')
                    return False

            file_path.write_text(
                sources.write_content,
                encoding='utf8')
            logger.info(f'執行成功。')
            return True
        except:
            logger.info(f'執行失敗。')
            logger.critical('\n' + traceback.format_exc())
Esempio n. 17
0
class Editor(QtWidgets.QWidget, Ui_ScriptEditor):
    sig_reload_log_list = QtCore.pyqtSignal()
    _data = Dict({
        'BASIC': {
            'activate': False,
            'start_hotkey': [121],
            'stop_hotkey': [123],
            'descript': '',
            'category': 'once'
        },
        'SCRIPT': {
            'content': []
        },
    })

    def __init__(self, script, path, *args, **kwargs):
        super().__init__()
        self.setupUi(self)
        self.script = script
        self.path = path
        self.lst_trigger.itemDoubleClicked.connect(
            lambda item: self.lst_page.setCurrentIndex(self.lst_trigger.
                                                       currentRow()))
        self.lst_trigger.sig_drop_new.connect(
            lambda class_name: self.add_trigger(class_name=class_name))
        self.lst_log.itemDoubleClicked.connect(self.fn_show_log)
        self.sig_reload_log_list.connect(self.fn_reload_log_list)
        if not self.path.is_dir():
            self.fn_reload_log_list()

    @property
    def data(self) -> Dict:
        if self.path.is_dir():
            return self._data

        if self.rb_once.isChecked():
            category = 'once'
        if self.rb_while.isChecked():
            category = 'while'

        return Dict({
            'BASIC': {
                'activate': bool(self.ccb_activate.currentIndex()),
                'start_hotkey': self.le_start_hotkey.PRESSED_KEY_VK,
                'stop_hotkey': self.le_stop_hotkey.PRESSED_KEY_VK,
                'descript': self.te_descript.toPlainText(),
                'category': category
            },
            'SCRIPT': {
                'content': [
                    self.lst_page.widget(row).manager.data
                    for row in range(self.lst_trigger.rowCount())
                ]
            },
        })

    @data.setter
    def data(self, data):
        self._data = data
        # 修改UI
        self.ccb_activate.setCurrentIndex(int(data.BASIC.activate))
        self.le_start_hotkey.PRESSED_KEY_VK = data.BASIC.start_hotkey
        self.le_stop_hotkey.PRESSED_KEY_VK = data.BASIC.stop_hotkey
        self.te_descript.setText(data.BASIC.descript)
        if data.BASIC.category == 'once':
            self.rb_once.click()
        else:
            self.rb_while.click()
        for i, d in enumerate(data.SCRIPT.content):
            self.add_trigger(class_name=d.class_name, data=d, row=i)

    @property
    def path(self) -> Path:
        return self.script.path

    @path.setter
    def path(self, path):
        self.script.path = path

    @property
    def tab_text(self):
        return self.script.tab_text

    # 腳本內容存讀
    def compare_data(self) -> bool:
        return self._data == self.data

    def reset_data(self):
        self.data = self._data

    def init_data(self):
        with self.path.open('w', encoding='utf8') as f:
            json.dump(self.data, f, ensure_ascii=False)

    def read_data(self):
        if self.path.is_dir():
            return
        if not self.path.read_text(encoding='utf8'):
            self.init_data()
            return
        # 讀取資料
        self.data = Dict.load_json(self.path, encoding='utf8')

    def save_data(self, path=None):
        path = path or self.path
        with path.open('w', encoding='utf8') as f:
            json.dump(self.data, f, ensure_ascii=False, indent=4)
        self._data = self.data

    def check_saved(self):
        self.script.rename_tab_text(self.script.tab_text)
        self.script.tree.setText(0, self.script.tab_text)

    # 腳本設計
    def move_up_trigger(self, manager):
        self.lst_page.removeWidget(manager.right)
        self.lst_trigger.removeRow(manager.row)
        self.add_trigger(class_name=manager.__class__.__name__,
                         row=manager.row - 1,
                         data=manager.data)

    def move_down_trigger(self, manager):
        self.lst_page.removeWidget(manager.right)
        self.lst_trigger.removeRow(manager.row)
        self.add_trigger(class_name=manager.__class__.__name__,
                         row=manager.row + 1,
                         data=manager.data)

    def remove_trigger(self, manager):
        self.lst_trigger.removeRow(manager.row)
        self.lst_page.removeWidget(manager.right)
        self.reset_index()
        self.check_saved()

    def add_trigger(self, class_name, row=None, data=None):
        Manager = getattr(trigger, class_name, None)
        if not Manager:
            return
        row = row or self.lst_trigger.currentRow()
        row = 0 if row == -1 else row

        # 實體化
        manager = Manager(self, data)
        # 清單內容設定
        self.lst_trigger.insertRow(row)
        self.lst_trigger.setVerticalHeaderItem(row, manager.header_item)
        self.lst_trigger.setCellWidget(row, 0, manager.handle_item)
        self.lst_trigger.setItem(row, 1, manager.name_item)
        # 調整高度
        self.lst_trigger.verticalHeader().resizeSection(row, 50)
        # 右側加入
        self.lst_page.insertWidget(row, manager.right)
        # 重新排序
        self.reset_index()

    # 重新排序腳本清單編號、設定清單按鈕是否啟用
    def reset_index(self):
        for row in range(self.lst_trigger.rowCount()):
            header_item = self.lst_trigger.verticalHeaderItem(row)
            header_item.setText(str(row))

            manager = self.lst_page.widget(row).manager
            manager.row = row
            manager.pb_move_up.setDisabled(False)
            manager.pb_move_down.setDisabled(False)

            if row == 0:
                manager.pb_move_up.setDisabled(True)

            if row + 1 == self.lst_trigger.rowCount():
                manager.pb_move_down.setDisabled(True)
        self.check_saved()

    def fn_reload_log_list(self):
        self.lst_log.clear()
        self.lst_log.addItems(
            map(
                lambda p: p.stem,
                Path(f'log/{self.path.parent.name}/{self.path.stem}').iterdir(
                )))

    def fn_show_log(self, item):
        log_path = Path(
            f'log/{self.path.parent.name}/{self.path.stem}/{item.text()}.log')
        txt = log_path.read_text(encoding='utf8')
        self.te_log.setText(txt)
Esempio n. 18
0
class ScreenCheckTrigger(BaseTrigger):
    TITLE = '圖片定位'
    INFORMATION = (
        '來源圖片: 前置步驟中截圖(開啟檔案)取得的圖片檔。\n'
        '目標圖片: 將要在來源圖片中定位的目標。\n'
        '灰階查找: 是否將圖片轉為灰階進行定位。\n'
        '信心度: 定位結果的信心度設定。'
    )
    DIC_DEFAULT = Dict({
        **BaseTrigger.DIC_DEFAULT,
        'other': {
            'source_img': {
                'class_name': 'FileEdit', 'name': '來源圖片',
                'method': 'getOpenFileName', 'types': 'All files(*)',
                'default': 1, 'fixed': 'data', 'variable': '0',
            },
            'target_img': {
                'class_name': 'FileEdit', 'name': '目標圖片',
                'method': 'getOpenFileName', 'types': 'All files(*)',
                'default': 1, 'fixed': 'data', 'variable': '0',
            },
            'grayscale': {
                'class_name': 'ComboBox', 'name': '灰階查找',
                'choices': ['否', '是'], 'fixed': 0, 'variable': '0',
            },
            'confidence': {
                'class_name': 'DoubleSpinBox', 'name': '信心度',
                'fixed': 0, 'variable': '1.0',
            },
        }
    })

    def get_top_left(self, n):
        _x, _y = pyautogui.position()
        pyautogui.moveTo(-n * pyautogui.size()[0], -n * pyautogui.size()[1])
        x, y = pyautogui.position()
        pyautogui.moveTo(_x, _y)
        return x, y

    def activate(self, logger, *args, **kwargs):
        try:
            logger.info(f'【{self.TITLE}】')
            sources = super().activate(*args, **kwargs)
            x, y = self.get_top_left(
                win32api.GetSystemMetrics(win32con.SM_CMONITORS))
            logger.info(f'螢幕定位最左上角為:{(x, y)}')
            pos = pyautogui.locate(
                sources.target_img,
                sources.source_img,
                grayscale=bool(sources.grayscale),
                confidence=sources.confidence,
            )
            logger.info(f'目標圖片位置定位為:{pos}')
            pos = pyautogui.center(pos)
            pos = (pos[0] + x, pos[1] + y)
            logger.info(f'目標圖片中心點定位為:{pos}')
            logger.info(f'執行成功。')
            return pos
        except:
            logger.info(f'執行失敗。')
            logger.critical('\n' + traceback.format_exc())