First commit
This commit is contained in:
commit
c9a76a4855
|
@ -0,0 +1,5 @@
|
|||
.idea/
|
||||
PyQt5/
|
||||
MangoSift.build/
|
||||
MangoSift.dist/
|
||||
MangoSift.output/
|
|
@ -0,0 +1,83 @@
|
|||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "MangoSift"
|
||||
#define MyAppExec "MangoSift"
|
||||
#define MyAppVersion "1.0.0.3"
|
||||
#define MyAppBuildDate "20250409"
|
||||
#define MyAppPublisher "zhaoyafan"
|
||||
#define MyAppPublisherURL "https://www.fanscloud.net/"
|
||||
|
||||
[Code]
|
||||
function IsProcessRunning(ExeFileName: string): Boolean;
|
||||
var
|
||||
FSWbemLocator: Variant;
|
||||
FWMIService: Variant;
|
||||
FWbemObject: Variant;
|
||||
begin
|
||||
Result := False;
|
||||
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
|
||||
FWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2');
|
||||
FWbemObject := FWMIService.ExecQuery(Format('SELECT * FROM Win32_Process Where Name = "%s"', [ExeFileName]));
|
||||
Result := not VarIsNull(FWbemObject) and (FWbemObject.Count > 0);
|
||||
end;
|
||||
|
||||
function InitializeSetup(): Boolean;
|
||||
begin
|
||||
if IsProcessRunning(ExpandConstant('{#MyAppExec}.exe')) then
|
||||
begin
|
||||
MsgBox('The main program is still running, please close it before installation.', mbError, MB_OK);
|
||||
Result := False;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{5B7674AB-32CF-4591-0FFE-6F4003984A3B}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppPublisherURL}
|
||||
VersionInfoProductName={#MyAppName}
|
||||
VersionInfoProductTextVersion={#MyAppVersion}
|
||||
VersionInfoProductVersion={#MyAppVersion}
|
||||
VersionInfoTextVersion={#MyAppVersion}
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DisableProgramGroupPage=yes
|
||||
OutputBaseFilename={#MyAppName} Setup {#MyAppVersion}
|
||||
OutputDir={#MyAppExec}.output
|
||||
SetupIconFile={#MyAppExec}.dist\favicon.ico
|
||||
UninstallDisplayIcon={app}\{#MyAppExec}.exe
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
CloseApplications=yes
|
||||
; ArchitecturesAllowed=x64compatible
|
||||
; ArchitecturesInstallIn64BitMode=x64compatible
|
||||
|
||||
[UninstallRun]
|
||||
Filename: "cmd.exe"; Parameters: "/C taskkill /IM {#MyAppExec}.exe /F"; Flags: runhidden
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "{#MyAppExec}.dist\{#MyAppExec}.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#MyAppExec}.dist\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExec}.exe"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExec}.exe"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExec}.exe"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
|
@ -0,0 +1,83 @@
|
|||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "%APPNAME%"
|
||||
#define MyAppExec "%APPEXEC%"
|
||||
#define MyAppVersion "%APPVERSION%"
|
||||
#define MyAppBuildDate "%APPBUILDDATE%"
|
||||
#define MyAppPublisher "%APPPUBLISHER%"
|
||||
#define MyAppPublisherURL "%APPPUBLISHERURL%"
|
||||
|
||||
[Code]
|
||||
function IsProcessRunning(ExeFileName: string): Boolean;
|
||||
var
|
||||
FSWbemLocator: Variant;
|
||||
FWMIService: Variant;
|
||||
FWbemObject: Variant;
|
||||
begin
|
||||
Result := False;
|
||||
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
|
||||
FWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2');
|
||||
FWbemObject := FWMIService.ExecQuery(Format('SELECT * FROM Win32_Process Where Name = "%s"', [ExeFileName]));
|
||||
Result := not VarIsNull(FWbemObject) and (FWbemObject.Count > 0);
|
||||
end;
|
||||
|
||||
function InitializeSetup(): Boolean;
|
||||
begin
|
||||
if IsProcessRunning(ExpandConstant('{#MyAppExec}.exe')) then
|
||||
begin
|
||||
MsgBox('The main program is still running, please close it before installation.', mbError, MB_OK);
|
||||
Result := False;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{5B7674AB-32CF-4591-0FFE-6F4003984A3B}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppPublisherURL}
|
||||
VersionInfoProductName={#MyAppName}
|
||||
VersionInfoProductTextVersion={#MyAppVersion}
|
||||
VersionInfoProductVersion={#MyAppVersion}
|
||||
VersionInfoTextVersion={#MyAppVersion}
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DisableProgramGroupPage=yes
|
||||
OutputBaseFilename={#MyAppName} Setup {#MyAppVersion}
|
||||
OutputDir={#MyAppExec}.output
|
||||
SetupIconFile={#MyAppExec}.dist\favicon.ico
|
||||
UninstallDisplayIcon={app}\{#MyAppExec}.exe
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
CloseApplications=yes
|
||||
%DISABLEX64%ArchitecturesAllowed=x64compatible
|
||||
%DISABLEX64%ArchitecturesInstallIn64BitMode=x64compatible
|
||||
|
||||
[UninstallRun]
|
||||
Filename: "cmd.exe"; Parameters: "/C taskkill /IM {#MyAppExec}.exe /F"; Flags: runhidden
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "{#MyAppExec}.dist\{#MyAppExec}.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#MyAppExec}.dist\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExec}.exe"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExec}.exe"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExec}.exe"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
|
@ -0,0 +1,691 @@
|
|||
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
|
||||
|
||||
|
||||
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__()
|
||||
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(90)
|
||||
|
||||
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('background-color: white; border: 1px solid gray;')
|
||||
self.image_frame.setFixedSize(380, 190)
|
||||
self.image_label = QLabel()
|
||||
self.image_image = QVBoxLayout()
|
||||
self.image_image.addWidget(self.image_label)
|
||||
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.adjustSize()
|
||||
self.hide()
|
||||
|
||||
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(250)
|
||||
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(380)
|
||||
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']]]
|
||||
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'],)))
|
||||
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.75)
|
||||
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.3'
|
||||
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']:
|
||||
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()
|
Binary file not shown.
After Width: | Height: | Size: 143 KiB |
Binary file not shown.
After Width: | Height: | Size: 169 KiB |
Binary file not shown.
After Width: | Height: | Size: 170 KiB |
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
Binary file not shown.
After Width: | Height: | Size: 168 KiB |
Binary file not shown.
After Width: | Height: | Size: 202 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,53 @@
|
|||
[defaults]
|
||||
base_features = sparse_super,large_file,filetype,resize_inode,dir_index,ext_attr
|
||||
default_mntopts = acl,user_xattr
|
||||
enable_periodic_fsck = 0
|
||||
blocksize = 4096
|
||||
inode_size = 256
|
||||
inode_ratio = 16384
|
||||
reserved_ratio = 1.0
|
||||
|
||||
[fs_types]
|
||||
ext3 = {
|
||||
features = has_journal
|
||||
}
|
||||
ext4 = {
|
||||
features = has_journal,extent,huge_file,dir_nlink,extra_isize,uninit_bg
|
||||
inode_size = 256
|
||||
}
|
||||
ext4dev = {
|
||||
features = has_journal,extent,huge_file,flex_bg,inline_data,64bit,dir_nlink,extra_isize
|
||||
inode_size = 256
|
||||
options = test_fs=1
|
||||
}
|
||||
small = {
|
||||
blocksize = 1024
|
||||
inode_size = 128
|
||||
inode_ratio = 4096
|
||||
}
|
||||
floppy = {
|
||||
blocksize = 1024
|
||||
inode_size = 128
|
||||
inode_ratio = 8192
|
||||
}
|
||||
big = {
|
||||
inode_ratio = 32768
|
||||
}
|
||||
huge = {
|
||||
inode_ratio = 65536
|
||||
}
|
||||
news = {
|
||||
inode_ratio = 4096
|
||||
}
|
||||
largefile = {
|
||||
inode_ratio = 1048576
|
||||
blocksize = -1
|
||||
}
|
||||
largefile4 = {
|
||||
inode_ratio = 4194304
|
||||
blocksize = -1
|
||||
}
|
||||
hurd = {
|
||||
blocksize = 4096
|
||||
inode_size = 128
|
||||
}
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue