Always open console on Windows (but hide it)
Otherwise program crashes when trying to communicate with subprocess (e.g. soapy_power) if started in pythonw.exe, bacause console is missing.
This commit is contained in:
parent
e9391aedc0
commit
9d21d49157
@ -2,4 +2,3 @@ include LICENSE
|
|||||||
include README.rst
|
include README.rst
|
||||||
include qspectrumanalyzer.desktop
|
include qspectrumanalyzer.desktop
|
||||||
include qspectrumanalyzer.png
|
include qspectrumanalyzer.png
|
||||||
include qspectrumanalyzer.svg
|
|
||||||
|
2
PKGBUILD
2
PKGBUILD
@ -2,7 +2,7 @@
|
|||||||
pkgname=qspectrumanalyzer
|
pkgname=qspectrumanalyzer
|
||||||
pkgver=2.1.0
|
pkgver=2.1.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power, rx_power, rtl_power, hackrf_sweep and other backends)"
|
pkgdesc="Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power, rtl_power, hackrf_sweep, rx_power and other backends)"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
url="https://github.com/xmikos/qspectrumanalyzer"
|
url="https://github.com/xmikos/qspectrumanalyzer"
|
||||||
license=('GPL3')
|
license=('GPL3')
|
||||||
|
@ -2,7 +2,7 @@ QSpectrumAnalyzer
|
|||||||
=================
|
=================
|
||||||
|
|
||||||
Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power,
|
Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power,
|
||||||
rx_power, rtl_power, hackrf_sweep and other backends)
|
rtl_power, hackrf_sweep, rx_power and other backends)
|
||||||
|
|
||||||
Screenshots
|
Screenshots
|
||||||
-----------
|
-----------
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from qspectrumanalyzer.__main__ import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
31
qspectrumanalyzer/__main__.py
Executable file → Normal file
31
qspectrumanalyzer/__main__.py
Executable file → Normal file
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys, signal, time
|
import sys, os, signal, time, argparse
|
||||||
|
|
||||||
from Qt import QtCore, QtGui, QtWidgets, __binding__
|
from Qt import QtCore, QtGui, QtWidgets, __binding__
|
||||||
|
|
||||||
@ -17,6 +17,8 @@ from qspectrumanalyzer.ui_qspectrumanalyzer_persistence import Ui_QSpectrumAnaly
|
|||||||
from qspectrumanalyzer.ui_qspectrumanalyzer_colors import Ui_QSpectrumAnalyzerColors
|
from qspectrumanalyzer.ui_qspectrumanalyzer_colors import Ui_QSpectrumAnalyzerColors
|
||||||
from qspectrumanalyzer.ui_qspectrumanalyzer import Ui_QSpectrumAnalyzerMainWindow
|
from qspectrumanalyzer.ui_qspectrumanalyzer import Ui_QSpectrumAnalyzerMainWindow
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
|
||||||
# Allow CTRL+C and/or SIGTERM to kill us (PyQt blocks it otherwise)
|
# Allow CTRL+C and/or SIGTERM to kill us (PyQt blocks it otherwise)
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
||||||
@ -240,6 +242,10 @@ class QSpectrumAnalyzerMainWindow(QtWidgets.QMainWindow, Ui_QSpectrumAnalyzerMai
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
|
# Set window icon
|
||||||
|
icon_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "qspectrumanalyzer.svg")
|
||||||
|
self.setWindowIcon(QtGui.QIcon(icon_path))
|
||||||
|
|
||||||
# Create plot widgets and update UI
|
# Create plot widgets and update UI
|
||||||
self.spectrumPlotWidget = SpectrumPlotWidget(self.mainPlotLayout)
|
self.spectrumPlotWidget = SpectrumPlotWidget(self.mainPlotLayout)
|
||||||
self.waterfallPlotWidget = WaterfallPlotWidget(self.waterfallPlotLayout, self.histogramPlotLayout)
|
self.waterfallPlotWidget = WaterfallPlotWidget(self.waterfallPlotLayout, self.histogramPlotLayout)
|
||||||
@ -612,7 +618,28 @@ class QSpectrumAnalyzerMainWindow(QtWidgets.QMainWindow, Ui_QSpectrumAnalyzerMai
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = QtWidgets.QApplication(sys.argv)
|
global debug
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
parser= argparse.ArgumentParser(
|
||||||
|
prog="qspectrumanalyzer",
|
||||||
|
description="Spectrum analyzer for multiple SDR platforms",
|
||||||
|
)
|
||||||
|
parser.add_argument("--debug", action="store_true",
|
||||||
|
help="detailed debugging messages")
|
||||||
|
parser.add_argument("--version", action="version",
|
||||||
|
version="%(prog)s {}".format(__version__))
|
||||||
|
args, unparsed_args = parser.parse_known_args()
|
||||||
|
debug = args.debug
|
||||||
|
|
||||||
|
# Hide console window on Windows
|
||||||
|
if sys.platform == 'win32' and not debug:
|
||||||
|
from qspectrumanalyzer import windows
|
||||||
|
if windows.is_attached_console_visible():
|
||||||
|
windows.set_attached_console_visible(False)
|
||||||
|
|
||||||
|
# Start PyQt application
|
||||||
|
app = QtWidgets.QApplication(sys.argv[:1] + unparsed_args)
|
||||||
app.setOrganizationName("QSpectrumAnalyzer")
|
app.setOrganizationName("QSpectrumAnalyzer")
|
||||||
app.setOrganizationDomain("qspectrumanalyzer.eutopia.cz")
|
app.setOrganizationDomain("qspectrumanalyzer.eutopia.cz")
|
||||||
app.setApplicationName("QSpectrumAnalyzer")
|
app.setApplicationName("QSpectrumAnalyzer")
|
||||||
|
@ -82,10 +82,11 @@ class BasePowerThread(QtCore.QThread):
|
|||||||
"""Terminate power process"""
|
"""Terminate power process"""
|
||||||
with self._shutdown_lock:
|
with self._shutdown_lock:
|
||||||
if self.process:
|
if self.process:
|
||||||
try:
|
if self.process.poll() is None:
|
||||||
self.process.terminate()
|
try:
|
||||||
except ProcessLookupError:
|
self.process.terminate()
|
||||||
pass
|
except ProcessLookupError:
|
||||||
|
pass
|
||||||
self.process.wait()
|
self.process.wait()
|
||||||
self.process = None
|
self.process = None
|
||||||
|
|
||||||
|
@ -140,13 +140,14 @@ class PowerThread(BasePowerThread):
|
|||||||
"""Stop soapy_power process"""
|
"""Stop soapy_power process"""
|
||||||
with self._shutdown_lock:
|
with self._shutdown_lock:
|
||||||
if self.process:
|
if self.process:
|
||||||
try:
|
if self.process.poll() is None:
|
||||||
if sys.platform == 'win32':
|
try:
|
||||||
self.process.send_signal(signal.CTRL_BREAK_EVENT)
|
if sys.platform == 'win32':
|
||||||
else:
|
self.process.send_signal(signal.CTRL_BREAK_EVENT)
|
||||||
self.process.terminate()
|
else:
|
||||||
except ProcessLookupError:
|
self.process.terminate()
|
||||||
pass
|
except ProcessLookupError:
|
||||||
|
pass
|
||||||
self.process.wait()
|
self.process.wait()
|
||||||
self.process = None
|
self.process = None
|
||||||
|
|
||||||
|
133
qspectrumanalyzer/qspectrumanalyzer.svg
Normal file
133
qspectrumanalyzer/qspectrumanalyzer.svg
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
id="svg4290"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
inkscape:export-filename="qspectrumanalyzer.png"
|
||||||
|
inkscape:export-xdpi="135"
|
||||||
|
inkscape:export-ydpi="135"
|
||||||
|
sodipodi:docname="qspectrumanalyzer.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4292" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="5.6568543"
|
||||||
|
inkscape:cx="0.51171273"
|
||||||
|
inkscape:cy="17.429759"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:grid-bbox="true"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:snap-intersection-paths="true"
|
||||||
|
inkscape:snap-bbox="false"
|
||||||
|
inkscape:snap-nodes="true"
|
||||||
|
inkscape:object-nodes="true"
|
||||||
|
inkscape:snap-global="false"
|
||||||
|
inkscape:window-width="1366"
|
||||||
|
inkscape:window-height="709"
|
||||||
|
inkscape:window-x="-4"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4298" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata4295">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer">
|
||||||
|
<rect
|
||||||
|
style="fill:#000000;stroke:#666666;stroke-opacity:1"
|
||||||
|
id="rect4300"
|
||||||
|
width="30"
|
||||||
|
height="30"
|
||||||
|
x="1"
|
||||||
|
y="1"
|
||||||
|
rx="3"
|
||||||
|
ry="3" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 16,1 0,30"
|
||||||
|
id="path4302"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 31,16 1,16"
|
||||||
|
id="path4302-5"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<use
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
xlink:href="#path4302"
|
||||||
|
id="use4369"
|
||||||
|
transform="translate(-7.5,0)"
|
||||||
|
width="100%"
|
||||||
|
height="100%" />
|
||||||
|
<use
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
xlink:href="#path4302"
|
||||||
|
id="use4371"
|
||||||
|
transform="translate(7.5,0)"
|
||||||
|
width="100%"
|
||||||
|
height="100%" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4373"
|
||||||
|
d="M 31,8.5 1,8.5"
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||||
|
<use
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
xlink:href="#path4302-5"
|
||||||
|
id="use4375"
|
||||||
|
transform="translate(0,7.5)"
|
||||||
|
width="100%"
|
||||||
|
height="100%" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#00ff00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 1.0625,25.25 c 0,0 2.5545099,5.432144 4.3470654,0.903939 0.6415968,-1.620748 1.1843693,-10.505075 2.3294368,-10.451145 1.124605,0.05297 1.2928473,9.79388 3.0231028,11.399454 0.836834,0.735662 2.019611,-2.789575 2.96484,-2.691821 1.178304,0.121857 2.125956,3.657855 3.181223,2.383073 2.07617,-2.508052 1.527752,-21.6078848 2.588897,-21.5746877 1.200441,0.037555 1.333727,19.4989257 3.765313,21.7522057 1.533625,1.421166 1.791478,-2.106359 3.115303,-2.461012 0.660576,-0.176968 2.034012,3.420657 2.702589,3.52241 0.772278,0.117536 1.624099,-1.153766 2.01348,-0.813666"
|
||||||
|
id="path4394"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="csscssssssc" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;stroke:#666666;stroke-opacity:1"
|
||||||
|
id="rect4300-8"
|
||||||
|
width="30"
|
||||||
|
height="30"
|
||||||
|
x="1"
|
||||||
|
y="1"
|
||||||
|
rx="3"
|
||||||
|
ry="3" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
48
qspectrumanalyzer/windows.py
Normal file
48
qspectrumanalyzer/windows.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright © Spyder Project Contributors
|
||||||
|
# Licensed under the terms of the MIT License
|
||||||
|
# (see spyder/__init__.py for details)
|
||||||
|
|
||||||
|
"""Windows-specific utilities"""
|
||||||
|
|
||||||
|
|
||||||
|
from ctypes import windll
|
||||||
|
|
||||||
|
|
||||||
|
# --- Window control ---
|
||||||
|
|
||||||
|
SW_SHOW = 5 # activate and display
|
||||||
|
SW_SHOWNA = 8 # show without activation
|
||||||
|
SW_HIDE = 0
|
||||||
|
|
||||||
|
GetConsoleWindow = windll.kernel32.GetConsoleWindow
|
||||||
|
ShowWindow = windll.user32.ShowWindow
|
||||||
|
IsWindowVisible = windll.user32.IsWindowVisible
|
||||||
|
|
||||||
|
# Handle to console window associated with current Python
|
||||||
|
# interpreter procss, 0 if there is no window
|
||||||
|
console_window_handle = GetConsoleWindow()
|
||||||
|
|
||||||
|
def set_attached_console_visible(state):
|
||||||
|
"""Show/hide system console window attached to current process.
|
||||||
|
Return it's previous state.
|
||||||
|
|
||||||
|
Availability: Windows"""
|
||||||
|
flag = {True: SW_SHOW, False: SW_HIDE}
|
||||||
|
return bool(ShowWindow(console_window_handle, flag[state]))
|
||||||
|
|
||||||
|
def is_attached_console_visible():
|
||||||
|
"""Return True if attached console window is visible"""
|
||||||
|
return IsWindowVisible(console_window_handle)
|
||||||
|
|
||||||
|
def set_windows_appusermodelid():
|
||||||
|
"""Make sure correct icon is used on Windows 7 taskbar"""
|
||||||
|
try:
|
||||||
|
return windll.shell32.SetCurrentProcessExplicitAppUserModelID("spyder.Spyder")
|
||||||
|
except AttributeError:
|
||||||
|
return "SetCurrentProcessExplicitAppUserModelID not found"
|
||||||
|
|
||||||
|
|
||||||
|
# [ ] the console state asks for a storage container
|
||||||
|
# [ ] reopen console on exit - better die open than become a zombie
|
33
setup.py
33
setup.py
@ -27,7 +27,7 @@ try:
|
|||||||
setup_entry_points = {
|
setup_entry_points = {
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
Executable('QSpectrumAnalyzer=qspectrumanalyzer.__main__:main',
|
Executable('QSpectrumAnalyzer=qspectrumanalyzer.__main__:main',
|
||||||
console=False, icon_file='qspectrumanalyzer.ico'),
|
console=True, icon_file='qspectrumanalyzer.ico'),
|
||||||
Executable('soapy_power=soapypower.__main__:main',
|
Executable('soapy_power=soapypower.__main__:main',
|
||||||
console=True),
|
console=True),
|
||||||
],
|
],
|
||||||
@ -39,7 +39,8 @@ except ImportError:
|
|||||||
setup(
|
setup(
|
||||||
name="QSpectrumAnalyzer",
|
name="QSpectrumAnalyzer",
|
||||||
version=__version__,
|
version=__version__,
|
||||||
description="Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power, rx_power, rtl_power, hackrf_sweep and other backends)",
|
description=("Spectrum analyzer for multiple SDR platforms "
|
||||||
|
"(PyQtGraph based GUI for soapy_power, rtl_power, hackrf_sweep, rx_power and other backends)"),
|
||||||
long_description=open('README.rst').read(),
|
long_description=open('README.rst').read(),
|
||||||
author="Michal Krenek (Mikos)",
|
author="Michal Krenek (Mikos)",
|
||||||
author_email="m.krenek@gmail.com",
|
author_email="m.krenek@gmail.com",
|
||||||
@ -48,19 +49,20 @@ setup(
|
|||||||
packages=["qspectrumanalyzer", "qspectrumanalyzer.backends"],
|
packages=["qspectrumanalyzer", "qspectrumanalyzer.backends"],
|
||||||
package_data={
|
package_data={
|
||||||
"qspectrumanalyzer": [
|
"qspectrumanalyzer": [
|
||||||
|
"qspectrumanalyzer.svg",
|
||||||
"*.ui",
|
"*.ui",
|
||||||
"languages/*.qm",
|
"languages/*.qm",
|
||||||
"languages/*.ts"
|
"languages/*.ts",
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
data_files=[
|
data_files=[
|
||||||
("share/applications", ["qspectrumanalyzer.desktop"]),
|
("share/applications", ["qspectrumanalyzer.desktop"]),
|
||||||
("share/pixmaps", ["qspectrumanalyzer.png"])
|
("share/pixmaps", ["qspectrumanalyzer.png"]),
|
||||||
],
|
],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"soapy_power>=1.5.0",
|
"soapy_power>=1.6.0",
|
||||||
"pyqtgraph>=0.10.0",
|
"pyqtgraph>=0.10.0",
|
||||||
"Qt.py"
|
"Qt.py",
|
||||||
],
|
],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
@ -75,17 +77,26 @@ setup(
|
|||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Topic :: Communications :: Ham Radio",
|
"Topic :: Communications :: Ham Radio",
|
||||||
"Topic :: Scientific/Engineering :: Visualization"
|
"Topic :: Scientific/Engineering :: Visualization",
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'build_qt': {
|
'build_qt': {
|
||||||
'packages': ['qspectrumanalyzer'],
|
'packages': ['qspectrumanalyzer'],
|
||||||
'languages': ['cs'],
|
'languages': ['cs'],
|
||||||
'replacement_bindings': 'Qt'
|
'replacement_bindings': 'Qt',
|
||||||
|
},
|
||||||
|
'build_exe': {
|
||||||
|
'datas': [
|
||||||
|
('qspectrumanalyzer/qspectrumanalyzer.svg', 'qspectrumanalyzer'),
|
||||||
|
('qspectrumanalyzer/*.ui', 'qspectrumanalyzer'),
|
||||||
|
('qspectrumanalyzer/languages/*.ts', 'qspectrumanalyzer/languages'),
|
||||||
|
('qspectrumanalyzer/languages/*.qm', 'qspectrumanalyzer/languages'),
|
||||||
|
('README.rst', '.'),
|
||||||
|
('LICENSE', '.'),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
'build_exe': {},
|
|
||||||
'bdist_msi': {
|
'bdist_msi': {
|
||||||
'upgrade_code': '30740ef4-84e7-4e67-8e4a-12b53492c387',
|
'upgrade_code': '{30740EF4-84E7-4E67-8E4A-12B53492C387}',
|
||||||
'shortcuts': [
|
'shortcuts': [
|
||||||
'ProgramMenuFolder\\QSpectrumAnalyzer=QSpectrumAnalyzer',
|
'ProgramMenuFolder\\QSpectrumAnalyzer=QSpectrumAnalyzer',
|
||||||
],
|
],
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from soapypower.__main__ import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
Loading…
Reference in New Issue
Block a user