758 lines
28 KiB
Python
758 lines
28 KiB
Python
import os
|
|
import re
|
|
import sys
|
|
import time
|
|
import json
|
|
import psutil
|
|
import shutil
|
|
import signal
|
|
import winreg
|
|
import hashlib
|
|
import inspect
|
|
import datetime
|
|
import tempfile
|
|
import platform
|
|
import threading
|
|
import subprocess
|
|
import http.client
|
|
import urllib.parse
|
|
import usb.core
|
|
import usb.backend.libusb0
|
|
from pathlib import Path
|
|
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox, QMenu, QAction, QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem, QLabel, QFrame
|
|
from PyQt5.QtCore import Qt, QCoreApplication, QPropertyAnimation, pyqtSignal, QEasingCurve, QPoint
|
|
from PyQt5.QtGui import QPixmap, QFont, QIcon, QPalette, QColor
|
|
|
|
|
|
def _fd(f):
|
|
return f.fileno() if hasattr(f, 'fileno') else f
|
|
|
|
|
|
if os.name == 'nt':
|
|
import msvcrt
|
|
from ctypes import (sizeof, c_ulong, c_void_p, c_int64, Structure, Union, POINTER, windll, byref)
|
|
from ctypes.wintypes import BOOL, DWORD, HANDLE
|
|
|
|
LOCK_SH = 0x0
|
|
LOCK_NB = 0x1
|
|
LOCK_EX = 0x2
|
|
LOCK_UN = 0x9
|
|
|
|
if sizeof(c_ulong) != sizeof(c_void_p):
|
|
ULONG_PTR = c_int64
|
|
else:
|
|
ULONG_PTR = c_ulong
|
|
|
|
PVOID = c_void_p
|
|
|
|
|
|
class _OFFSET(Structure):
|
|
_fields_ = [
|
|
('Offset', DWORD),
|
|
('OffsetHigh', DWORD)
|
|
]
|
|
|
|
|
|
class _OFFSET_UNION(Union):
|
|
_fields_ = [
|
|
('_offset', _OFFSET),
|
|
('Pointer', PVOID)
|
|
]
|
|
_anonymous_ = ['_offset']
|
|
|
|
|
|
class OVERLAPPED(Structure):
|
|
_fields_ = [
|
|
('Internal', ULONG_PTR),
|
|
('InternalHigh', ULONG_PTR),
|
|
('_offset_union', _OFFSET_UNION),
|
|
('hEvent', HANDLE)
|
|
]
|
|
_anonymous_ = ['_offset_union']
|
|
|
|
|
|
LPOVERLAPPED = POINTER(OVERLAPPED)
|
|
LockFileEx = windll.kernel32.LockFileEx
|
|
LockFileEx.restype = BOOL
|
|
LockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
|
UnlockFileEx = windll.kernel32.UnlockFileEx
|
|
UnlockFileEx.restype = BOOL
|
|
UnlockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
|
|
|
|
|
def flock(f, flags):
|
|
hfile = msvcrt.get_osfhandle(_fd(f))
|
|
overlapped = OVERLAPPED()
|
|
if flags == LOCK_UN:
|
|
ret = UnlockFileEx(
|
|
hfile,
|
|
0,
|
|
0,
|
|
0xFFFF0000,
|
|
byref(overlapped)
|
|
)
|
|
else:
|
|
ret = LockFileEx(
|
|
hfile,
|
|
flags,
|
|
0,
|
|
0,
|
|
0xFFFF0000,
|
|
byref(overlapped)
|
|
)
|
|
return bool(ret)
|
|
else:
|
|
try:
|
|
import fcntl
|
|
|
|
LOCK_SH = fcntl.LOCK_SH
|
|
LOCK_NB = fcntl.LOCK_NB
|
|
LOCK_EX = fcntl.LOCK_EX
|
|
LOCK_UN = fcntl.LOCK_UN
|
|
except (ImportError, AttributeError):
|
|
LOCK_EX = LOCK_SH = LOCK_NB = 0
|
|
|
|
|
|
def flock(f, flags):
|
|
return flags == LOCK_UN
|
|
else:
|
|
def flock(f, flags):
|
|
return fcntl.flock(_fd(f), flags) == 0
|
|
|
|
|
|
class ShellResult(dict):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.code = self.stdout = self.stderr = None
|
|
|
|
def __setattr__(self, key, value):
|
|
pass
|
|
|
|
def __getitem__(self, item):
|
|
try:
|
|
return super().__getitem__(item)
|
|
except Exception:
|
|
return None
|
|
|
|
def __getattr__(self, item):
|
|
try:
|
|
return super().__getitem__(item)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
class ShellExecution:
|
|
@classmethod
|
|
def exec(cls, command: str = '', stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=0.0, env=None):
|
|
stdout = stdout or subprocess.DEVNULL
|
|
stderr = stderr or subprocess.DEVNULL
|
|
env = env or os.environ
|
|
try:
|
|
result = subprocess.run(command, shell=True, stdout=stdout, stderr=stderr, timeout=timeout or None, env=env)
|
|
stdout_text = ''
|
|
stderr_text = ''
|
|
if result.stdout:
|
|
try:
|
|
stdout_text = bytes(result.stdout).decode('utf-8')
|
|
except Exception:
|
|
pass
|
|
if result.stderr:
|
|
try:
|
|
stderr_text = bytes(result.stderr).decode('utf-8')
|
|
except Exception:
|
|
pass
|
|
return ShellResult({
|
|
'code': result.returncode,
|
|
'stdout': stdout_text,
|
|
'stderr': stderr_text
|
|
})
|
|
except subprocess.TimeoutExpired:
|
|
return ShellResult({
|
|
'code': 1,
|
|
'stdout': '',
|
|
'stderr': 'Execution timeout.'
|
|
})
|
|
|
|
|
|
class HTTPResponse:
|
|
status: int
|
|
reason: str
|
|
result: str
|
|
|
|
def __init__(self, response):
|
|
self.status = response[0]
|
|
self.reason = response[1]
|
|
self.result = response[2]
|
|
|
|
def json(self):
|
|
try:
|
|
return json.loads(self.result)
|
|
except json.decoder.JSONDecodeError:
|
|
return None
|
|
|
|
|
|
class HTTPRequest:
|
|
@classmethod
|
|
def get(cls, url='', header=None, timeout=15.0) -> HTTPResponse:
|
|
url = str(url)
|
|
header = header or {}
|
|
header = {**{'User-Agent': 'Python'}, **header}
|
|
host = urllib.parse.urlparse(url).netloc
|
|
conn = http.client.HTTPSConnection(host, timeout=timeout) if urllib.parse.urlparse(url).scheme == 'https' else http.client.HTTPConnection(host, timeout=timeout)
|
|
conn.request('GET', url, None, header)
|
|
response = conn.getresponse()
|
|
response_data = response.read().decode('utf-8')
|
|
conn.close()
|
|
return HTTPResponse((response.status, response.reason, response_data))
|
|
|
|
@classmethod
|
|
def post(cls, url='', data=None, json_data=None, header=None, timeout=15.0) -> HTTPResponse:
|
|
url = str(url)
|
|
header = header or {}
|
|
data = json.dumps(json_data) if json_data else (data and str(data).encode('utf-8'))
|
|
header = {**{'User-Agent': 'Python'}, **header}
|
|
host = urllib.parse.urlparse(url).netloc
|
|
conn = http.client.HTTPSConnection(host, timeout=timeout) if urllib.parse.urlparse(url).scheme == 'https' else http.client.HTTPConnection(host, timeout=timeout)
|
|
conn.request('POST', url, data, header)
|
|
response = conn.getresponse()
|
|
response_data = response.read().decode('utf-8')
|
|
conn.close()
|
|
return HTTPResponse((response.status, response.reason, response_data))
|
|
|
|
|
|
def calculate_md5(input_string):
|
|
md5_hash = hashlib.md5(input_string.encode()).hexdigest()
|
|
return md5_hash
|
|
|
|
|
|
def get_date_timestamp():
|
|
return datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
|
|
|
|
|
class FloatingWindow(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
dark_palette = QPalette()
|
|
dark_palette.setColor(QPalette.Window, QColor(62, 62, 62))
|
|
self.setPalette(dark_palette)
|
|
self.setStyleSheet('')
|
|
self.setStyleSheet("""
|
|
QMainWindow {
|
|
border: 1px solid #626262;
|
|
}
|
|
QDialog {
|
|
border: 1px solid #626262;
|
|
}
|
|
QLabel {
|
|
color: #FCFCFC;
|
|
}
|
|
QLineEdit {
|
|
background: #2A2A2A;
|
|
border: 2px solid #444;
|
|
border-radius: 5px;
|
|
padding: 5px;
|
|
color: white;
|
|
selection-color: rgba(255, 255, 255, 0.95);
|
|
selection-background-color: rgba(245, 245, 245, 0.15);
|
|
}
|
|
QTableWidget {
|
|
background: #3f3f3f;
|
|
border: 2px solid #6c6c6c;
|
|
border-radius: 5px;
|
|
padding: 5px;
|
|
color: white;
|
|
}
|
|
QHeaderView::section {
|
|
background-color: #444;
|
|
color: white;
|
|
padding: 4px;
|
|
border: 1px solid #555;
|
|
}
|
|
QTableWidget::item {
|
|
padding: 5px;
|
|
}
|
|
QPushButton {
|
|
background: #444;
|
|
padding: 5px 10px;
|
|
border-radius: 4px;
|
|
color: white;
|
|
}
|
|
QPushButton:hover {
|
|
background: #555;
|
|
}
|
|
""")
|
|
self.animation = None
|
|
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool)
|
|
self.setAttribute(Qt.WA_TranslucentBackground, False)
|
|
self.setWindowTitle('Product Information')
|
|
self.setMinimumWidth(380)
|
|
self.layout = QVBoxLayout()
|
|
self.layout.setContentsMargins(4, 4, 4, 4)
|
|
self.setLayout(self.layout)
|
|
|
|
self.table = QTableWidget(0, 2)
|
|
self.table.setEditTriggers(QTableWidget.NoEditTriggers)
|
|
self.table.horizontalHeader().setVisible(False)
|
|
self.table.verticalHeader().setVisible(False)
|
|
self.table.setShowGrid(True)
|
|
self.table.horizontalHeader().setStretchLastSection(True)
|
|
self.table.setFixedHeight(125)
|
|
|
|
font = QFont('Consolas', 9)
|
|
self.table.setFont(font)
|
|
self.table.verticalHeader().setDefaultSectionSize(20)
|
|
self.table.setColumnWidth(0, 64)
|
|
self.table.setColumnWidth(1, 280)
|
|
self.layout.addWidget(self.table)
|
|
self.image_frame = QFrame()
|
|
self.image_frame.setFrameShape(QFrame.Box)
|
|
self.image_frame.setLineWidth(1)
|
|
self.image_frame.setStyleSheet('border: 1px solid gray;')
|
|
self.image_frame.setFixedSize(380, 190)
|
|
self.image_label = QLabel()
|
|
self.image_label.setAlignment(Qt.AlignCenter)
|
|
self.image_image = QVBoxLayout()
|
|
self.image_image.addStretch()
|
|
self.image_image.addWidget(self.image_label)
|
|
self.image_image.addStretch()
|
|
self.image_frame.setLayout(self.image_image)
|
|
self.layout.addWidget(self.image_frame, 0, Qt.AlignCenter)
|
|
self.alert_label = QLabel()
|
|
self.alert_label.setAlignment(Qt.AlignLeft)
|
|
self.alert_label.setStyleSheet('padding: 0px 2px 0px 2px; color: red; font-family: \"Microsoft YaHei\";')
|
|
self.alert_label.setFixedSize(380, 16)
|
|
self.layout.addWidget(self.alert_label)
|
|
|
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
self.customContextMenuRequested.connect(self.show_context_menu)
|
|
|
|
self.adjustSize()
|
|
self.hide()
|
|
|
|
def show_context_menu(self, pos):
|
|
menu = QMenu(self)
|
|
close_action = QAction('关闭窗口', self)
|
|
close_action.triggered.connect(self.hide_window)
|
|
menu.addAction(close_action)
|
|
menu.exec_(self.mapToGlobal(pos))
|
|
|
|
def show(self):
|
|
return super().show()
|
|
|
|
def hide(self):
|
|
self.set_table_data(None)
|
|
self.set_image(None)
|
|
self.set_alert(None)
|
|
return super().hide()
|
|
|
|
def set_table_data(self, data):
|
|
data = data or []
|
|
self.table.clearContents()
|
|
self.table.setRowCount(len(data))
|
|
for row, (k, v) in enumerate(data):
|
|
k_item = QTableWidgetItem(k)
|
|
k_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
k_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
|
v_item = QTableWidgetItem(v)
|
|
v_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
v_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
|
self.table.setItem(row, 0, k_item)
|
|
self.table.setItem(row, 1, v_item)
|
|
|
|
def set_image(self, path):
|
|
if (path and os.path.exists(path)) == 1:
|
|
pixmap = QPixmap(path)
|
|
pixmap = pixmap.scaled(self.image_frame.size(), aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
|
|
self.image_frame.show()
|
|
self.image_label.setPixmap(pixmap)
|
|
else:
|
|
self.image_label.setPixmap(QPixmap())
|
|
self.adjustSize()
|
|
|
|
def set_alert(self, text):
|
|
if (not text) == 0:
|
|
self.alert_label.show()
|
|
self.alert_label.setText(text)
|
|
else:
|
|
self.alert_label.setText('')
|
|
self.adjustSize()
|
|
|
|
def show_window(self):
|
|
if (self.isVisible()) == 0:
|
|
screen_geometry = QApplication.desktop().screenGeometry()
|
|
x = (screen_geometry.width() - self.width()) // 2
|
|
y = (screen_geometry.height() - 40)
|
|
s_pos = QPoint(x, y)
|
|
e_pos = QPoint(x, screen_geometry.height() - self.height() - 40)
|
|
self.move(s_pos)
|
|
self.show()
|
|
self.animation = QPropertyAnimation(self, b'pos')
|
|
self.animation.setDuration(450)
|
|
self.animation.setStartValue(s_pos)
|
|
self.animation.setEndValue(e_pos)
|
|
self.animation.setEasingCurve(QEasingCurve.OutQuad)
|
|
self.animation.start()
|
|
|
|
def hide_window(self):
|
|
if (self.isVisible()) == 1:
|
|
c_pos = self.pos()
|
|
e_pos = QPoint(c_pos.x(), QApplication.desktop().screenGeometry().height())
|
|
self.animation = QPropertyAnimation(self, b'pos')
|
|
self.animation.setDuration(520)
|
|
self.animation.setStartValue(c_pos)
|
|
self.animation.setEndValue(e_pos)
|
|
self.animation.setEasingCurve(QEasingCurve.InQuad)
|
|
self.animation.finished.connect(self.hide)
|
|
self.animation.start()
|
|
else:
|
|
self.hide()
|
|
|
|
|
|
class MainWindow(QSystemTrayIcon):
|
|
signal_on_current_device_changed = pyqtSignal(int, str)
|
|
|
|
def __init__(self, app_name, app_version):
|
|
self.app_name = app_name
|
|
self.app_version = app_version
|
|
self.running = True
|
|
self.floating_window = None
|
|
self.current_device_no_connection = (0, '')
|
|
self.current_device_property = self.current_device_no_connection
|
|
super().__init__(QIcon(os.path.join(os.path.dirname(__file__), 'favicon.ico')), None)
|
|
self.setToolTip('正在监听设备连接')
|
|
self.tray_menu = QMenu()
|
|
action_list = [
|
|
['退出程序', self.exit]
|
|
]
|
|
for action_item in action_list:
|
|
action = QAction(action_item[0], self)
|
|
action.triggered.connect(action_item[1])
|
|
self.tray_menu.addAction(action)
|
|
self.setContextMenu(self.tray_menu)
|
|
self.show()
|
|
self.init()
|
|
self.signal_on_current_device_changed.connect(self.on_current_device_changed)
|
|
threading.Thread(target=self.run_task, daemon=True).start()
|
|
|
|
def init(self):
|
|
if (self.floating_window is None) == 1:
|
|
self.floating_window = FloatingWindow()
|
|
|
|
def exit(self):
|
|
self.running = False
|
|
self.floating_window and self.floating_window.close()
|
|
self.exit_cleanup()
|
|
QApplication.quit()
|
|
|
|
@staticmethod
|
|
def exit_cleanup():
|
|
for proc in psutil.process_iter():
|
|
try:
|
|
proc.name().lower() == 'adb.exe' and proc.terminate()
|
|
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
pass
|
|
|
|
@property
|
|
def current_device(self):
|
|
return self.current_device_property
|
|
|
|
@current_device.setter
|
|
def current_device(self, value):
|
|
if value == self.current_device_property:
|
|
return
|
|
try:
|
|
self.current_device_property = value
|
|
self.signal_on_current_device_changed.emit(value[0], value[1])
|
|
except Exception as e:
|
|
print(e, file=sys.stderr)
|
|
|
|
@staticmethod
|
|
def get_barcode() -> str:
|
|
finds = re.findall(
|
|
'Converted string: 02.Y.GG.([0-9A-Z.]+[0-9]{6})',
|
|
ShellExecution.exec(command='adb shell ggec_ft read -main_pcbid', timeout=1.5).stdout
|
|
)
|
|
if finds:
|
|
return finds[0]
|
|
else:
|
|
return ''
|
|
|
|
@staticmethod
|
|
def get_chip_id() -> str:
|
|
finds = re.findall(
|
|
'([0-9A-Za-z]{16,32})\\s*fastboot',
|
|
ShellExecution.exec(command='fastboot devices', timeout=1.0).stdout
|
|
)
|
|
if finds:
|
|
return finds[0]
|
|
else:
|
|
return ''
|
|
|
|
@staticmethod
|
|
def get_chip_id_usb() -> str:
|
|
try:
|
|
backend = usb.backend.libusb0.get_backend(find_library=lambda x: 'C:\\Windows\\System32\\libusb0.dll')
|
|
devices = usb.core.find(find_all=True, backend=backend)
|
|
for d in devices:
|
|
if d.idVendor == 0x1B8E and d.idProduct == 0xC004 and d.iSerialNumber:
|
|
return usb.util.get_string(d, d.iSerialNumber)
|
|
return ''
|
|
except Exception:
|
|
return ''
|
|
|
|
def update_device(self):
|
|
device_methods = {self.get_chip_id_usb: 1, self.get_chip_id: 2, self.get_barcode: 3}
|
|
device = self.current_device_no_connection
|
|
for method, value in device_methods.items():
|
|
d = method()
|
|
if d:
|
|
device = (value, d)
|
|
break
|
|
try:
|
|
self.current_device = device
|
|
except Exception as e:
|
|
print(e, file=sys.stderr)
|
|
|
|
@staticmethod
|
|
def gsc_api_fetch(url: str, app_type: str, app_code: str, app_sign: str, data):
|
|
date_timestamp = get_date_timestamp()
|
|
send_data = json.dumps(data)
|
|
try:
|
|
response = HTTPRequest.post(
|
|
url,
|
|
json_data={
|
|
'type': app_type, 'code': app_code, 'sign': app_sign, 'time': date_timestamp, 'param': send_data, 'token': calculate_md5('%s%s%s' % (send_data, app_sign, date_timestamp))
|
|
},
|
|
header={
|
|
'Content-Type': 'application/json'
|
|
},
|
|
timeout=1.5
|
|
)
|
|
info = response.json().get('info') or response.json().get('error')
|
|
if (not info) == 1:
|
|
return response.json()['data']
|
|
raise Exception(info)
|
|
except TypeError:
|
|
raise Exception('Failed to obtain data.')
|
|
except TimeoutError:
|
|
raise Exception('Access timeout.')
|
|
except OSError:
|
|
raise Exception('Failed to get data from the server.')
|
|
|
|
def lookup_info_from_mes(self, code: str):
|
|
request_info = 'lcm08GetBsInfoByQrCode'
|
|
request_area = 'lcm08Area'
|
|
request_code = 'LCM08'
|
|
request_sign = '3CA08BA06C6C2EC70A3E7834CE8A127B'
|
|
request_path = 'http://10.130.97.102:8814/itf/api'
|
|
code = code.strip()
|
|
info = self.gsc_api_fetch(request_path, request_info, request_code, request_sign, {{38: 'barcode', 14: 'deviceId'}.get(len(code), 'chipId'): code})
|
|
area = self.gsc_api_fetch(request_path, request_area, request_code, request_sign, {'qrCode': info['qrCode']})
|
|
data = {**info, **{'model': area['model'], 'color': area['color'], 'region': area['region'], 'localization': area['localization']}}
|
|
return {'MAIN_BARCODE': data['qrCode'], 'DID': data['deviceId'], 'CHIP_ID': data['chipId'], 'FW_VER': data['firmwareVersion1'], 'MODEL': data['model'], 'COLOR': data['color']}
|
|
|
|
def on_current_device_changed(self, device_type: int, device_id: str):
|
|
if device_type > 0:
|
|
try:
|
|
if device_type in [1, 2, 3, 4, 5]:
|
|
print('%s%s' % ('', 'Device connected'))
|
|
self.floating_window and self.floating_window.show_window()
|
|
self.floating_window and device_type == 1 and self.floating_window.set_alert('正在通过%s连接...' % ('USB',))
|
|
self.floating_window and device_type == 2 and self.floating_window.set_alert('正在通过%s连接...' % ('FASTBOOT',))
|
|
self.floating_window and device_type == 3 and self.floating_window.set_alert('正在通过%s连接...' % ('ADB',))
|
|
data = self.lookup_info_from_mes(device_id)
|
|
except Exception as e:
|
|
data = None
|
|
if e:
|
|
print('%s%s' % ('', e))
|
|
self.floating_window and self.floating_window.set_alert('%s' % (e,))
|
|
if (data is not None) == 1:
|
|
table_data = [['BARCODE', data['MAIN_BARCODE']], ['DID', data['DID']], ['MODEL', data['MODEL']], ['COLOR', data['COLOR']], ['FIRMWARE', data['FW_VER']]]
|
|
try:
|
|
if table_data:
|
|
self.floating_window and self.floating_window.set_table_data(table_data)
|
|
self.floating_window and self.floating_window.set_image(os.path.join(os.path.dirname(__file__), '%s.PNG' % (data['MODEL'],)))
|
|
if not bool(re.match(open(os.path.join(os.path.dirname(__file__), '%s.REGEXP' % (data['MODEL'],)), 'r', encoding='utf-8').read(), data['DID'])):
|
|
self.floating_window and self.floating_window.set_alert('DID:%s与产品型号:%s不匹配,请检查~' % (data['DID'], data['MODEL'],))
|
|
self.floating_window and self.floating_window.set_image(os.path.join(os.path.dirname(__file__), 'UNKNOWN.PNG'))
|
|
except Exception:
|
|
pass
|
|
else:
|
|
print('获取数据失败')
|
|
else:
|
|
try:
|
|
if device_type in [0]:
|
|
print('%s%s' % ('', 'Device disconnected'))
|
|
self.floating_window and self.floating_window.hide_window()
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
def run_task(self):
|
|
try:
|
|
while self.running:
|
|
time.sleep(0.55)
|
|
self.update_device()
|
|
except KeyboardInterrupt:
|
|
print('Monitor has exited.')
|
|
self.exit()
|
|
exit(0)
|
|
|
|
|
|
class MainRunner:
|
|
def __init__(self):
|
|
signal.signal(signal.SIGINT, self._handle_interrupt)
|
|
self.app_name = 'MangoSift'
|
|
self.app_version = '1.0.0.5'
|
|
self.app_publisher = 'zhaoyafan'
|
|
self.app_publisher_url = 'https://www.fanscloud.net/'
|
|
self.application = None
|
|
self.window = None
|
|
|
|
def _copy_files_and_directories(self, src, dst):
|
|
function_name = inspect.currentframe().f_code.co_name
|
|
if (os.path.exists(src)) != 1:
|
|
return None
|
|
if (os.path.isdir(src)) == 1:
|
|
if not os.path.exists(dst):
|
|
os.makedirs(dst)
|
|
for item in os.listdir(src):
|
|
s = os.path.join(src, item)
|
|
d = os.path.join(dst, item)
|
|
if os.path.isdir(s):
|
|
self.__getattribute__(function_name)(s, d)
|
|
else:
|
|
shutil.copy(s, d)
|
|
else:
|
|
shutil.copy(src, dst)
|
|
|
|
@staticmethod
|
|
def add_startup(entry_name, exe_path):
|
|
if not os.path.exists(exe_path):
|
|
return None
|
|
try:
|
|
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\Microsoft\\Windows\\CurrentVersion\\Run', 0, winreg.KEY_SET_VALUE)
|
|
winreg.SetValueEx(key, entry_name, 0, winreg.REG_SZ, exe_path)
|
|
winreg.CloseKey(key)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
@staticmethod
|
|
def del_startup(entry_name):
|
|
try:
|
|
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\Microsoft\\Windows\\CurrentVersion\\Run', 0, winreg.KEY_SET_VALUE)
|
|
winreg.DeleteValue(key, entry_name)
|
|
winreg.CloseKey(key)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
def run(self):
|
|
exec_home = os.path.dirname(__file__)
|
|
exec_name = os.path.splitext(os.path.basename(__file__))[0]
|
|
self.add_startup(exec_name, os.path.join(exec_home, '%s.exe' % (exec_name,)))
|
|
os.environ['PATH'] = '%s%s%s' % (os.path.abspath('%s%s' % (os.path.dirname(__file__), '/platform-tools')), os.pathsep, os.environ['PATH'])
|
|
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
|
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
|
self.application = QApplication(sys.argv)
|
|
self.window = MainWindow(app_name=self.app_name, app_version=self.app_version)
|
|
sys.exit(self.application.exec_())
|
|
|
|
def build(self):
|
|
if (str(__file__).endswith('.py')) == 0:
|
|
print('Build is not currently supported.', file=sys.stderr)
|
|
exit(1)
|
|
from_home = os.path.dirname(os.path.abspath(__file__))
|
|
dist_home = os.path.join(os.path.dirname(__file__), '%s.dist' % (os.path.splitext(os.path.basename(__file__))[0],))
|
|
ask = input('%s %s: ' % ('Build?', '[Y/n]'))
|
|
if ask.lower().strip() == 'y':
|
|
subprocess.run(['pip', 'install', 'nuitka', '-q'], shell=True, env=os.environ)
|
|
subprocess.run([
|
|
'python',
|
|
'-m',
|
|
'nuitka',
|
|
'--standalone',
|
|
'--enable-plugin=pyqt5',
|
|
'--include-module=PyQt5',
|
|
'--windows-console-mode=disable',
|
|
'--windows-icon-from-ico=favicon.ico',
|
|
'--product-name=%s' % (self.app_name,),
|
|
'--file-description=%s' % (self.app_name,),
|
|
'--product-version=%s' % (self.app_version,),
|
|
'--copyright=Copyright (C) 2025',
|
|
'--output-dir=%s' % (os.path.join(os.path.dirname(__file__)),),
|
|
'%s' % (__file__,)
|
|
], shell=True, env=os.environ)
|
|
for i in ['PyQt5', 'platform-tools', 'favicon.ico', 'PSY02355001.PNG', 'PSY02355002.PNG', 'PSY02355003.PNG', 'PSY02355004.PNG', 'PSY02355005.PNG', 'UNKNOWN.PNG', 'PSY02355001.REGEXP', 'PSY02355002.REGEXP', 'PSY02355003.REGEXP', 'PSY02355004.REGEXP', 'PSY02355005.REGEXP']:
|
|
self._copy_files_and_directories('%s/%s' % (from_home, i), '%s/%s' % (dist_home, i))
|
|
else:
|
|
if (not os.path.exists(dist_home)) == 1:
|
|
return None
|
|
ask = input('%s %s: ' % ('Compile setup program?', '[Y/n]'))
|
|
if ask.lower().strip() == 'y':
|
|
compile_file = os.path.join(os.path.dirname(__file__), '%s.iss' % (os.path.splitext(os.path.basename(__file__))[0],))
|
|
compile_template = os.path.join(os.path.dirname(__file__), '%s.iss.template' % (os.path.splitext(os.path.basename(__file__))[0],))
|
|
compiler = 'C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe'
|
|
if (os.path.exists(compile_template)) != 1:
|
|
print('The template file \"%s\" does not exist.' % (compile_template,), file=sys.stderr)
|
|
return None
|
|
if (os.path.exists(compiler)) != 1:
|
|
print('The compiler \"%s\" does not exist. Please check if Inno Setup is installed. You can download it at https://www.innosetup.com/' % (compiler,),
|
|
file=sys.stderr)
|
|
return None
|
|
Path(compile_file).write_text(
|
|
Path(compile_template).read_text().replace(
|
|
'%APPNAME%',
|
|
self.app_name
|
|
).replace(
|
|
'%APPEXEC%',
|
|
os.path.splitext(os.path.basename(__file__))[0]
|
|
).replace(
|
|
'%APPVERSION%',
|
|
self.app_version
|
|
).replace(
|
|
'%APPBUILDDATE%',
|
|
time.strftime('%Y%m%d', time.localtime())
|
|
).replace(
|
|
'%APPPUBLISHER%',
|
|
self.app_publisher
|
|
).replace(
|
|
'%APPPUBLISHERURL%',
|
|
self.app_publisher_url
|
|
).replace(
|
|
'%DISABLEX64%',
|
|
'' if platform.architecture()[0] == '64bit' else '; '
|
|
)
|
|
)
|
|
subprocess.run([compiler, compile_file])
|
|
|
|
def _handle_interrupt(self, _signal, _frame):
|
|
print('Exit.', file=sys.stderr)
|
|
self.handle_interrupt()
|
|
|
|
def handle_interrupt(self):
|
|
try:
|
|
self.window.exit()
|
|
except Exception as e:
|
|
print(e, file=sys.stderr)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if (os.path.basename(__file__).lower().endswith('.int')) == 1:
|
|
QCoreApplication.addLibraryPath(os.path.join(os.path.dirname(__file__), 'site-packages/PyQt5/Qt5/plugins'))
|
|
else:
|
|
QCoreApplication.addLibraryPath(os.path.join(os.path.dirname(__file__), 'PyQt5/Qt5/plugins'))
|
|
f_lock = open(file=os.path.join(tempfile.gettempdir(), '%s.lock' % hashlib.md5(bytes(__file__, encoding='utf-8')).hexdigest()[:16]), mode='w', encoding='utf-8')
|
|
if (not flock(f_lock, LOCK_EX | LOCK_NB)) == 1:
|
|
app = QApplication(sys.argv)
|
|
msg = QMessageBox()
|
|
msg.setIcon(QMessageBox.Warning)
|
|
msg.setText('The application is already running.')
|
|
msg.setWindowTitle('Warning')
|
|
msg.setStandardButtons(QMessageBox.Cancel)
|
|
msg.exec_()
|
|
app.exit(1)
|
|
sys.exit(1)
|
|
else:
|
|
MainRunner().run() if (len(sys.argv) > 1 and sys.argv[1] == '--build') == 0 else MainRunner().build()
|