First commit
This commit is contained in:
commit
ce8fcbf913
|
@ -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={{7E345AAD-FB40-CAF0-B0FC-3A4CBD445240}
|
||||
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,483 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import ctypes
|
||||
import shutil
|
||||
import signal
|
||||
import winreg
|
||||
import hashlib
|
||||
import inspect
|
||||
import tempfile
|
||||
import platform
|
||||
import threading
|
||||
import subprocess
|
||||
import http.client
|
||||
import urllib.parse
|
||||
import datetime
|
||||
from wmi import WMI
|
||||
from tzlocal import get_localzone
|
||||
from pytz import timezone
|
||||
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox, QMenu, QAction
|
||||
from PyQt5.QtCore import Qt, QCoreApplication, QTimer
|
||||
from PyQt5.QtGui import QIcon
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
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
|
||||
|
||||
def request_windows_admin():
|
||||
if not ctypes.windll.shell32.IsUserAnAdmin():
|
||||
exec_home = os.path.dirname(__file__)
|
||||
exec_name = os.path.splitext(os.path.basename(__file__))[0]
|
||||
exec_path = os.path.join(exec_home, '%s.exe' % (exec_name,))
|
||||
if os.path.exists(exec_path):
|
||||
executable_path = exec_path
|
||||
else:
|
||||
executable_path = sys.executable
|
||||
executable_args = "\x20".join(sys.argv)
|
||||
print('runas admin: %s %s' % (executable_path, executable_args), file=sys.stderr)
|
||||
ctypes.windll.shell32.ShellExecuteW(None, 'runas', executable_path, executable_args, None, 1)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class HTTPResponse:
|
||||
status: int
|
||||
reason: str
|
||||
result: str
|
||||
header: dict
|
||||
def __init__(self, response):
|
||||
self.status = response[0]
|
||||
self.reason = response[1]
|
||||
self.result = response[2]
|
||||
self.header = response[3]
|
||||
|
||||
def json(self):
|
||||
try:
|
||||
return json.loads(self.result)
|
||||
except json.decoder.JSONDecodeError:
|
||||
return None
|
||||
|
||||
|
||||
class HTTPRequest:
|
||||
user_agent: str = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
|
||||
|
||||
@classmethod
|
||||
def _parse_headers(cls, headers):
|
||||
return {key.lower(): value for key, value in headers}
|
||||
|
||||
@classmethod
|
||||
def get(cls, url='', header=None, timeout=15) -> HTTPResponse:
|
||||
url = str(url)
|
||||
header = header or {}
|
||||
header = {**{'User-Agent': cls.user_agent}, **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')
|
||||
response_headers = cls._parse_headers(response.getheaders())
|
||||
conn.close()
|
||||
return HTTPResponse((response.status, response.reason, response_data, response_headers))
|
||||
|
||||
@classmethod
|
||||
def post(cls, url='', data=None, json_data=None, header=None, timeout=15) -> 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': cls.user_agent}, **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')
|
||||
response_headers = cls._parse_headers(response.getheaders())
|
||||
conn.close()
|
||||
return HTTPResponse((response.status, response.reason, response_data, response_headers))
|
||||
|
||||
@classmethod
|
||||
def head(cls, url='', header=None, timeout=15) -> HTTPResponse:
|
||||
url = str(url)
|
||||
header = header or {}
|
||||
header = {**{'User-Agent': cls.user_agent}, **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('HEAD', url, None, header)
|
||||
response = conn.getresponse()
|
||||
response_data = response.read().decode('utf-8')
|
||||
response_headers = cls._parse_headers(response.getheaders())
|
||||
conn.close()
|
||||
return HTTPResponse((response.status, response.reason, response_data, response_headers))
|
||||
|
||||
|
||||
class MainWindow(QSystemTrayIcon):
|
||||
def __init__(self, app_name, app_version):
|
||||
self.app_name = app_name
|
||||
self.app_version = app_version
|
||||
self.last_sync_time = 0
|
||||
self.last_sync_exception = ''
|
||||
self.is_synchronizing = None
|
||||
super().__init__(QIcon(os.path.join(os.path.dirname(__file__), 'favicon.ico')), None)
|
||||
self.setToolTip('')
|
||||
self.tray_icon_update_timer = QTimer(self)
|
||||
self.tray_icon_update_timer.timeout.connect(self.on_tray_icon_update)
|
||||
self.tray_icon_update_timer.start(1500)
|
||||
self.time_sync_update_timer = QTimer(self)
|
||||
self.time_sync_update_timer.timeout.connect(self.sync)
|
||||
self.time_sync_update_timer.start(1000 * 3600)
|
||||
QTimer.singleShot(1000 * 12, self.sync)
|
||||
self.tray_menu = QMenu()
|
||||
action_list = [
|
||||
['立即同步', self.sync],
|
||||
['退出程序', 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()
|
||||
|
||||
|
||||
def init(self):
|
||||
self.sync()
|
||||
|
||||
@staticmethod
|
||||
def exit():
|
||||
QApplication.quit()
|
||||
|
||||
def _sync_execution(self):
|
||||
try:
|
||||
self.is_synchronizing = 1
|
||||
dt = self.get_server_datetime()
|
||||
set_date = dt and self.set_date(dt[0])
|
||||
set_time = dt and self.set_time(dt[1])
|
||||
if (set_date and set_time) == 1:
|
||||
self.last_sync_time = self.get_runtime()
|
||||
self.last_sync_exception = ''
|
||||
else:
|
||||
self.last_sync_exception = '操作失败,可能需要以管理员身份运行'
|
||||
except Exception as e:
|
||||
if 'server' in str(e).lower():
|
||||
self.last_sync_exception = '网络错误'
|
||||
finally:
|
||||
self.is_synchronizing = 0
|
||||
|
||||
def sync(self):
|
||||
if (not self.is_synchronizing) == 1:
|
||||
print('Synchronizing...')
|
||||
threading.Thread(target=self._sync_execution).start()
|
||||
|
||||
@staticmethod
|
||||
def format_time(secs):
|
||||
return '刚刚' if secs < 60 else '%s分钟前' % ((secs // 60),) if secs < 3600 else '%s小时前' % (round(secs / 3600),)
|
||||
|
||||
@staticmethod
|
||||
def get_runtime():
|
||||
return time.perf_counter()
|
||||
|
||||
@staticmethod
|
||||
def set_date(date_str):
|
||||
try:
|
||||
process = subprocess.run('date %s' % (date_str,), shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)
|
||||
if process.returncode == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def set_time(time_str):
|
||||
try:
|
||||
process = subprocess.run('time %s' % (time_str,), shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)
|
||||
if process.returncode == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_server_datetime():
|
||||
try:
|
||||
gate = WMI().Win32_NetworkAdapterConfiguration(IPEnabled=True)[0].DefaultIPGateway[0]
|
||||
except Exception:
|
||||
gate = None
|
||||
server_list = ['cloud.tencent.com', '1.1.1.2', gate]
|
||||
date_string = ''
|
||||
for server in server_list:
|
||||
if server is not None:
|
||||
try:
|
||||
date_string = HTTPRequest.head(url='http://%s/' % (server,), timeout=1).header.get('date')
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if not date_string:
|
||||
raise Exception('Failed to get time from server.')
|
||||
ori_timezone = timezone('GMT')
|
||||
tar_timezone = timezone(str(get_localzone()))
|
||||
try:
|
||||
date_out = ori_timezone.localize(datetime.datetime.strptime(date_string, '%a, %d %b %Y %H:%M:%S %Z')).astimezone(tar_timezone) + datetime.timedelta(seconds=1)
|
||||
if (int(date_out.timestamp()) > 1735689600) == 1:
|
||||
return str(date_out.date()), str(date_out.time())
|
||||
raise Exception('The time returned by the server is older.')
|
||||
except ValueError:
|
||||
raise Exception('Failed to get time from server.')
|
||||
|
||||
def on_tray_icon_update(self):
|
||||
self.setToolTip('%s %s\n上次同步:%s' % (self.app_name, self.app_version, self.last_sync_exception or self.format_time(int(self.get_runtime() - self.last_sync_time))))
|
||||
|
||||
|
||||
class MainRunner:
|
||||
def __init__(self):
|
||||
signal.signal(signal.SIGINT, self._handle_interrupt)
|
||||
self.app_name = '时间同步助手'
|
||||
self.app_version = '1.0.0.0'
|
||||
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):
|
||||
request_windows_admin()
|
||||
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,)))
|
||||
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', 'favicon.ico']:
|
||||
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()
|
|
@ -0,0 +1,3 @@
|
|||
@echo off
|
||||
@chcp 65001
|
||||
@python TimeMaster.py --build
|
Binary file not shown.
After Width: | Height: | Size: 198 KiB |
Binary file not shown.
Loading…
Reference in New Issue