2 Commits
Author SHA1 Message Date
James C. Ahlstrom 1c209457dc Quisk version 4.2.41 2025-01-30 13:48:38 -05:00
James C. Ahlstrom 7e8a0846f7 Quisk version 4.2.40 2024-11-15 16:39:47 -05:00
8 changed files with 263 additions and 100 deletions
+14
View File
@@ -1,3 +1,17 @@
Quisk Version 4.2.41 January 2025
==================================
I made some improvements to the top-level Config/Radios screen. There is now an option to
make a new radio by copying settings from an existing radio. I worked around a bug in wxPython
and gtk3 that caused drawing problems on the config screens. This problem is not present on Windows.
Quisk Version 4.2.40 November 2024
===================================
Python 3.13 eliminated the telnetlib module, so I wrote a replacement. I fixed a bug in window sizing.
If you use the Dx Cluster feature, please report any problems. Dx Cluster used to use the telnetlib module
but is now new code.
I updated the install to Python 3.13. Please report any problems with Python 3.13.
Quisk Version 4.2.39 October 2024
==================================
I added an optional Hardware method PollGuiControl(self). If you define this in your Hardware file,
+1 -1
View File
@@ -1,2 +1,2 @@
#Quisk version 4.2.39
#Quisk version 4.2.41
from .quisk import main
+138 -47
View File
@@ -48,6 +48,7 @@ name2format = {
# Increasing the software version will display a message to re-read the soapy device.
soapy_software_version = 3
wxpython_gtk3_bug = 0
def FormatKhz(dnum): # Round to 3 decimal places; remove ending ".000"
t = "%.3f" % dnum
@@ -384,10 +385,20 @@ class Configuration:
app.bottom_widgets = dct['BottomWidgets'](app, hardware, conf, frame, gbs, vertBox)
return True
def OnPageChanging(self, event): # Called when the top level page changes (not RadioNotebook pages)
global wxpython_gtk3_bug
event.Skip()
notebook = event.GetEventObject()
index = event.GetSelection()
page = notebook.GetPage(index)
if sys.platform != 'win32': # Work around a bug in wxPython 4.2.1 and gtk3
w, h = application.main_frame.GetSize() # Change main window size by +/- one pixel to recalculate sizes
if wxpython_gtk3_bug:
wxpython_gtk3_bug = 0
h += 1
else:
wxpython_gtk3_bug = 1
h -= 1
application.main_frame.SetSize((w, h))
if isinstance(page, RadioNotebook):
if not page.pages:
page.MakePages()
@@ -419,6 +430,14 @@ class Configuration:
elif udp > 0:
return 'HiQSDR'
return 'SoftRock USB'
def FindNotebookPage(self, name):
for index in range(self.radios_page_start, self.notebk.GetPageCount()):
tab_text = self.notebk.GetPageText(index)
if tab_text[0] == '*':
tab_text = tab_text[1:-1]
if tab_text == name:
return index
return None
def AddRadio(self, radio_name, typ):
radio_dict = {}
radio_dict['hardware_file_type'] = typ
@@ -435,13 +454,24 @@ class Configuration:
page = RadioNotebook(self.notebk, radio_name)
self.notebk.AddPage(page, radio_name)
return True
def CopyRadio(self, old_name, new_name):
radio_dict = {}
index = Settings[2].index(old_name)
radio_dict.update(Settings[3][index])
Settings[2].append(new_name)
Settings[3].append(radio_dict)
page = RadioNotebook(self.notebk, new_name)
self.notebk.AddPage(page, new_name)
return True
def RenameRadio(self, old, new):
index = Settings[2].index(old)
n = self.radios_page_start + index
n = self.FindNotebookPage(old)
if n is None:
return
if old == Settings[1]:
self.notebk.SetPageText(n, "*%s*" % new)
else:
self.notebk.SetPageText(n, new)
index = Settings[2].index(old)
Settings[2][index] = new
self.notebk.GetPage(n).NewName(new)
if old == "ConfigFileRadio":
@@ -452,12 +482,10 @@ class Configuration:
index = Settings[2].index(name)
del Settings[2][index]
del Settings[3][index]
try:
n = self.radios_page_start + index
n = self.FindNotebookPage(name)
if n is not None:
self.notebk.DeletePage(n)
except:
pass
return True
return True
def GetRadioDict(self, radio_name=None): # None radio_name means the current radio
if radio_name:
index = Settings[2].index(radio_name)
@@ -1295,6 +1323,7 @@ class RadioNotebook(wx.Notebook): # The second-level notebook for each radio nam
self.SetBackgroundColour(parent.bg_color)
self.radio_name = radio_name
self.pages = []
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanging)
def MakePages(self):
radio_name = self.radio_name
radio_dict = local_conf.GetRadioDict(radio_name)
@@ -1325,6 +1354,20 @@ class RadioNotebook(wx.Notebook): # The second-level notebook for each radio nam
self.radio_name = new_name
for page in self.pages:
page.radio_name = new_name
def OnPageChanging(self, event):
global wxpython_gtk3_bug
event.Skip()
index = event.GetSelection()
page = self.GetPage(index)
if sys.platform != 'win32': # Work around a bug in wxPython 4.2.1 and gtk3
w, h = application.main_frame.GetSize() # Change main window size by +/- one pixel to recalculate sizes
if wxpython_gtk3_bug:
wxpython_gtk3_bug = 0
h += 1
else:
wxpython_gtk3_bug = 1
h -= 1
application.main_frame.SetSize((w, h))
class ChoiceCombo(wx.Choice):
text_for_blank = "-blank-"
@@ -1439,7 +1482,13 @@ class ComboCtrl(wxcombo.ComboCtrl):
wm = w
wm += charx * 5
self.SetSizeHints(wm, self.height, 9999, self.height)
def SetValue(self, value):
wxcombo.ComboCtrl.SetValue(self, value)
self.SetBackgroundColour(self.bgColor)
self.Refresh()
def SetSelection(self, n): # Set text to item in list box. Name conflict with wxcombo.ComboCtrl.
self.SetBackgroundColour(self.bgColor)
self.Refresh()
try:
text = self.choices[n]
except IndexError:
@@ -2328,9 +2377,9 @@ class ControlMixin:
else:
return value
class BaseWindow(wx.ScrolledCanvas, ControlMixin):
class BaseWindow(wx.ScrolledWindow, ControlMixin):
def __init__(self, parent):
wx.ScrolledCanvas.__init__(self, parent)
wx.ScrolledWindow.__init__(self, parent)
ControlMixin.__init__(self, parent)
class ConfigConfig(BaseWindow):
@@ -2653,17 +2702,21 @@ class Radios(BaseWindow): # The "Radios" first-level page
def __init__(self, parent):
BaseWindow.__init__(self, parent)
self.SetBackgroundColour(parent.bg_color)
self.num_cols = 8
self.num_cols = 5
self.radio_name = None
self.cur_radio_text = self.AddTextL(1, 'xx', self.num_cols - 1)
self.SetCurrentRadioText()
self.index = 0
txt = 'Each "radio" is a block of settings that describes a radio to use with Quisk.'
self.AddTextL(1, txt, self.num_cols - 1)
self.NextRow()
self.NextRow()
item = self.AddTextL(1, "When Quisk starts, use the radio")
self.radio_buttons = []
rb = self.AddRadioButton(1, "When Quisk starts, use the radio", self.NewTop, start=True)
rb.SetValue(True)
self.start_radio = self.AddComboCtrl(2, 'big_radio_name', choices=[], no_edit=True)
self.start_radio.handler = self.OnChoiceStartup
self.radio_buttons.append((rb, self.start_radio))
self.NextRow()
item = self.AddTextL(1, "Add a new radio with the general type")
rb = self.AddRadioButton(1, "Add a new radio with the general type", self.NewTop)
choices = []
for name, data in local_conf.receiver_data:
choices.append(name)
@@ -2671,30 +2724,60 @@ class Radios(BaseWindow): # The "Radios" first-level page
self.add_type = self.AddComboCtrl(2, '', choices=choices, no_edit=True)
self.add_type.SetSelection(0)
item = self.AddTextL(3, "and name the new radio")
self.add_name = self.AddComboCtrl(4, '', choices=["My Radio", "SR with XVtr", "SoftRock"])
item = self.AddPushButton(5, "Add", self.OnBtnAdd)
self.add_name = self.AddComboCtrl(4, '', choices=["My Radio", "Transverter", "SoftRock", "HL2"])
self.radio_buttons.append((rb, self.add_type, self.add_name))
self.NextRow()
item = self.AddTextL(1, "Rename the radio named")
rb = self.AddRadioButton(1, "Add a new radio copied from radio", self.NewTop)
self.add_from = self.AddComboCtrl(2, 'big_radio_name', choices=[], no_edit=True)
item = self.AddTextL(3, "and name the new radio")
self.add_name2 = self.AddComboCtrl(4, '', choices=["My Radio", "Transverter", "SoftRock", "HL2"])
self.radio_buttons.append((rb, self.add_from, self.add_name2))
self.NextRow()
rb = self.AddRadioButton(1, "Rename the radio named", self.NewTop)
self.rename_old = self.AddComboCtrl(2, 'big_radio_name', choices=[], no_edit=True)
item = self.AddTextL(3, "to the new name")
self.rename_new = self.AddComboCtrl(4, '', choices=["My Radio", "SR with XVtr", "SoftRock"])
item = self.AddPushButton(5, "Rename", self.OnBtnRename)
self.rename_new = self.AddComboCtrl(4, '', choices=["My Radio", "Transverter", "SoftRock", "HL2"])
self.radio_buttons.append((rb, self.rename_old, self.rename_new))
self.NextRow()
item = self.AddTextL(1, "Delete the radio named")
rb = self.AddRadioButton(1, "Delete the radio named", self.NewTop)
self.delete_name = self.AddComboCtrl(2, 'big_radio_name', choices=[], no_edit=True)
item = self.AddPushButton(3, "Delete", self.OnBtnDelete)
self.radio_buttons.append((rb, self.delete_name))
self.NextRow()
self.FitInside()
self.SetScrollRate(1, 1)
self.NextRow()
self.apply_btn = self.AddPushButton(3, "Apply", self.OnBtnApply)
self.NewRadioNames()
def SetCurrentRadioText(self):
radio_dict = local_conf.GetRadioDict(self.radio_name)
radio_type = radio_dict['hardware_file_type']
if Settings[1] == "ConfigFileRadio":
text = 'The current radio is ConfigFileRadio, so all settings come from the config file. The hardware type is %s.' % radio_type
self.NewTop()
self.SetScrollRate(1, 1)
def NewTop(self, event=None):
if event:
btn = event.GetEventObject()
else: # set first button
btn = self.radio_buttons[0][0]
for i in range(len(self.radio_buttons)):
data = self.radio_buttons[i]
rb = data[0]
if rb == btn:
self.index = i
for c in data[1:]:
c.Enable(rb == btn)
if self.index == 0:
self.apply_btn.Enable(False)
else:
text = "Quisk is running with settings from the radio %s. The hardware type is %s." % (Settings[1], radio_type)
self.cur_radio_text.SetLabel(text)
self.apply_btn.Enable(True)
def OnBtnApply(self, event):
index = self.index
ok = False
if index == 1: # add from hardware type
ok = self.OnBtnAdd()
elif index == 2: # add from existing radio
ok = self.OnBtnCopy()
elif index == 3: # rename
ok = self.OnBtnRename()
elif index == 4: # delete
ok = self.OnBtnDelete()
if ok:
self.radio_buttons[0][0].SetValue(True)
self.NewTop()
def DuplicateName(self, name):
if name in Settings[2] or name == "ConfigFileRadio":
dlg = wx.MessageDialog(self, "The name already exists. Please choose a different name.",
@@ -2703,34 +2786,39 @@ class Radios(BaseWindow): # The "Radios" first-level page
dlg.Destroy()
return True
return False
def OnBtnAdd(self, event):
def OnBtnAdd(self):
name = self.add_name.GetValue().strip()
if not name or self.DuplicateName(name):
return
self.add_name.SetValue('')
typ = self.add_type.GetValue().strip()
if local_conf.AddRadio(name, typ):
if Settings[0] != "Ask me":
Settings[0] = name
self.NewRadioNames()
local_conf.settings_changed = True
def OnBtnRename(self, event):
return True
def OnBtnCopy(self):
new_name = self.add_name2.GetValue().strip()
if not new_name or self.DuplicateName(new_name):
return
self.add_name2.SetValue('')
old_name = self.add_from.GetValue()
if local_conf.CopyRadio(old_name, new_name):
self.NewRadioNames()
local_conf.settings_changed = True
return True
def OnBtnRename(self):
old = self.rename_old.GetValue()
new = self.rename_new.GetValue().strip()
if not old or not new or self.DuplicateName(new):
return
self.rename_new.SetValue('')
if local_conf.RenameRadio(old, new):
if old == 'ConfigFileRadio' and Settings[1] == "ConfigFileRadio":
if Settings[1] == old:
Settings[1] = new
elif Settings[1] == old:
Settings[1] = new
self.SetCurrentRadioText()
if Settings[0] != "Ask me":
Settings[0] = new
self.NewRadioNames()
local_conf.settings_changed = True
def OnBtnDelete(self, event):
return True
def OnBtnDelete(self):
name = self.delete_name.GetValue()
if not name:
return
@@ -2742,6 +2830,7 @@ class Radios(BaseWindow): # The "Radios" first-level page
if ret == wx.ID_OK and local_conf.DeleteRadio(name):
self.NewRadioNames()
local_conf.settings_changed = True
return True
def OnChoiceStartup(self, ctrl):
choice = self.start_radio.GetValue()
if Settings[0] != choice:
@@ -2751,6 +2840,8 @@ class Radios(BaseWindow): # The "Radios" first-level page
choices = Settings[2][:] # can rename any available radio
self.rename_old.SetItems(choices)
self.rename_old.SetSelection(0)
self.add_from.SetItems(choices)
self.add_from.SetSelection(0)
if "ConfigFileRadio" in choices: # can not delete ConfigFileRadio
choices.remove("ConfigFileRadio")
self.delete_name.SetItems(choices)
@@ -2762,13 +2853,13 @@ class Radios(BaseWindow): # The "Radios" first-level page
try: # Set text in control
index = choices.index(Settings[0]) # last used radio, or new or renamed radio
except:
num = len(Settings[2])
if len == 0:
index = 1
elif num == 1:
index = 0
num = len(Settings[2]) # number of radios
if num == 0: # no radios
index = 1 # use ConfigFileRadio
elif num == 1: # one radio
index = 0 # use the one radio
else:
index = len(choices) - 2
index = len(choices) - 2 # use the last radio
Settings[0] = choices[index]
self.start_radio.SetSelection(index)
+14 -3
View File
@@ -194,7 +194,7 @@ Quisk is free open source software written in Python and C. It is hosted on the
<br>
<p>
If you have Python 3 installed on your computer, just continue to use that version.
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11 or 3.12.</b>
<b>On Windows Quisk requires Python 3.8, 3.9, 3.10, 3.11, 3.12 or 3.13.</b>
If you want to install a more recent version of Python, uninstall the old version first.
Running multiple versions of Python is possible but can be confusing and is not necessary.
A Quisk installation is needed for each version of Python you have.
@@ -204,7 +204,7 @@ A Quisk installation is needed for each version of Python you have.
<p>
Windows does not include Python, so you must first install a recent version of 64-bit Python 3.
If you already have 64-bit Python 3 installed, just keep using it.
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11 or 3.12.</b>
<b>On Windows Quisk requires Python 3.8, 3.9, 3.10, 3.11, 3.12 or 3.13.</b>
There may be newer versions of Python, but until support is added, use the versions listed above.
Python is available from <a href="http://www.python.org/">http://www.python.org</a>.
There is an option to <b>Add Python to PATH</b>. You MUST select
@@ -256,6 +256,7 @@ If you change to that directory you can run Quisk with "pythonw quisk.py".
<br>
<p>
To get started you must tell Quisk what kind of radio hardware you have. Press the Config button and select Radios.
Add a radio from the list of hardware radios available. Or add the best match.
Then set your sound devices for that radio; the device for the radio speakers, the microphone and so forth. All configuration
is (mostly) from the Config button. Ignore old directions and don't bother with a config file.
</p>
@@ -385,6 +386,7 @@ You run Quisk by running the file quisk.py with python3. For example, "python3 /
You can create a desktop icon to do that, but the method depends on your version of Linux.
<b>Never run Quisk with root privelege by using sudo. Fix the permission problem instead.</b>
To get started you must tell Quisk what kind of radio hardware you have. Press the Config button and select Radios.
Add a radio from the list of hardware radios available. Or add the best match.
Then set your sound devices for that radio; the device for the radio speakers, the microphone and so forth. All configuration
is (mostly) from the Config button. Ignore old directions and don't bother with a config file.
To uninstall Quisk, just delete the quisk directory.
@@ -532,7 +534,7 @@ When you first install Quisk, you will not have any settings for your radio.
Press the Config button and go to the Radios screen.
Then create a radio by specifying the general hardware type and give it a name of your choosing.
For a Hermes-Lite, specify "Hermes" as the hardware type and call it "HL2" (or some other name).
Press "Add" and a new tab for your radio will appear. Look through the various settings on the HL2 tab.
Press "Apply" and a new tab for your radio will appear. Look through the various settings on the HL2 tab.
The parameters for the radios are stored in the file quisk_settings.json.
<br>
<br>
@@ -1493,16 +1495,24 @@ at his site <a href="https://dk1mi.radio/quisk-fldigi/">https://dk1mi.radio/quis
If your digital program only uses Alsa devices or you don't want to use PulseAudio,
you can use the ALSA loopback device.
It works the same way as the Windows VAC. First create the loopback device with this command:
<br>
<br>
<pre>
sudo modprobe snd-aloop index=19
</pre>
<br>
<p>
The "index" is the card number, and it is best to use a number higher than your regular cards.
The loopback device will be gone on the next restart.
You can create the loopback device every time the system starts, but the way to do that depends on your version of Linux.
You could add this line to /etc/modprobe.d/alsa-base.conf:
</p>
<br>
<pre>
options snd-aloop index=19
</pre>
<br>
<p>
Or you could add snd-aloop to /etc/modules or put the modprobe command in /etc/rc.local instead.
Restart Linux. Now you can enter "cat /proc/asound/cards" to
print out your sound cards, and you should see a "Loopback" card listed.
@@ -1517,6 +1527,7 @@ Your audio is now connected and you
should be able to receive digital signals. Be sure to test your
transmit signal off the air. You may need to reduce power to
improve linearity.
</p>
<br>
<br>
+87 -39
View File
@@ -1,9 +1,9 @@
from __future__ import absolute_import
# This code was contributed by Christof, DJ4CM. Many Thanks!!
# Modified November 2024 by N2ADR to remove deprecated telnetlib module.
import threading
import socket
import time
import telnetlib
import quisk_conf_defaults as conf
class DxEntry:
@@ -81,61 +81,109 @@ class DxEntry:
class DxCluster(threading.Thread):
def __init__(self):
self.do_init = 1
self.error = 'Starting'
self.dxSpots = []
threading.Thread.__init__(self)
self.doQuit = threading.Event()
self.dxSpots = []
self.doQuit.clear()
self.dxLock = threading.Lock()
self.addr = conf.dxClHost + ':' + str(conf.dxClPort)
self.msg_no_spots = "No DX Cluster data from " + self.addr
self.msg_one_spot = '1 DX spot received from ' + self.addr
self.msg_spots = ' DX spots received from ' + self.addr
def run(self):
self.telnetInit()
self.telnetConnect()
if self.error:
self.sock.close()
return
while not self.doQuit.isSet():
try:
self.telnetRead()
by = self.sock.recv(1024)
except TimeoutError:
continue
except:
self.tn.close()
time.sleep(20)
by = b''
if by:
with self.dxLock:
self.bytes += by
else:
self.sock.close()
self.error = "Restarting " + self.addr
time.sleep(2)
if not self.doQuit.isSet():
self.telnetConnect()
self.tn.close()
def setListener (self, listener):
self.listener = listener
if self.error:
self.sock.close()
return
self.sock.close()
def telnetInit(self):
self.tn = telnetlib.Telnet()
def telnetConnect(self):
self.bytes = bytearray(0)
self.error = 'Starting'
self.sock = socket.socket()
self.sock.settimeout(20)
try:
self.sock.connect( (conf.dxClHost, conf.dxClPort) )
except:
self.error = "Failed to connect to " + self.addr
return
self.sock.settimeout(1)
for i in range(10):
try:
self.tn.open(conf.dxClHost, conf.dxClPort, 10)
self.tn.read_until(b"login:", 10)
self.tn.write(conf.user_call_sign.encode('utf-8', errors='ignore') + b"\n") # user_call_sign may be Unicode
break
self.bytes += self.sock.recv(1024)
except:
time.sleep(0.5)
pass
if b"login:" in self.bytes:
break
else:
self.error = 'No "login:" prompt from ' + self.addr
return
self.sock.sendall(conf.user_call_sign.encode('utf-8', errors='ignore') + b"\r\n")
if conf.dxClPassword:
self.tn.read_until(b"Password: ")
self.tn.write(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\n")
for i in range(10):
try:
self.bytes += self.sock.recv(1024)
except:
pass
if b"Password:" in self.bytes:
break
else:
self.error = 'No "Password:" prompt from ' + self.addr
return
self.sock.sendall(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\r\n")
self.error = ''
self.bytes = bytearray(0)
def telnetRead(self):
message = self.tn.read_until(b'\n', 60).decode(encoding='utf-8', errors='replace')
if self.doQuit.isSet() == False:
dxEntry = DxEntry();
if dxEntry.parseMessage(message):
for i, listElement in enumerate(self.dxSpots):
if (listElement.equal(dxEntry)):
listElement.join (dxEntry)
return
if listElement.isExpired():
del (self.dxSpots[i])
self.dxSpots.append(dxEntry)
if self.listener:
self.listener()
def getHost(self):
return self.tn.host + ':' + str(self.tn.port)
def Poll(self):
with self.dxLock:
index = self.bytes.find(b"\n")
if index >= 0:
message = self.bytes[0:index + 1]
self.bytes = self.bytes[index + 1:]
else:
return
message = message.decode(encoding='utf-8', errors='replace')
dxEntry = DxEntry()
if dxEntry.parseMessage(message):
for i, listElement in enumerate(self.dxSpots):
if (listElement.equal(dxEntry)):
listElement.join (dxEntry)
return True
if listElement.isExpired():
del (self.dxSpots[i])
self.dxSpots.append(dxEntry)
return True
def stop(self):
self.doQuit.set()
def dxStatus(self):
if self.error:
return self.error
nSpots = len(self.dxSpots)
if nSpots == 0:
return self.msg_no_spots
elif nSpots == 1:
return self.msg_one_spot
return str(nSpots) + self.msg_spots
+2
View File
@@ -402,6 +402,8 @@ class Hardware(BaseHardware):
if do_tx:
try:
self.rx_udp_socket.send(s)
if DEBUG:
self.PrintStatus(' Send', self.want_udp_status)
except:
pass
def SetVNA(self, key_down=None, vna_start=None, vna_stop=None, vna_count=None, do_tx=False):
+6 -9
View File
@@ -1434,12 +1434,8 @@ class ConfigStatus(wx.ScrolledCanvas):
self.MakeRow2("Sample interrupts", self.interrupts, cfile)
self.MakeRow2("Microphone or DGT level dB", level, qfile)
self.MakeRow2("FFT number of points", self.fft_size, err_msg)
if conf.dxClHost: # connection to dx cluster
nSpots = len(application.dxCluster.dxSpots)
if nSpots > 0:
msg = str(nSpots) + ' DX spot' + ('' if nSpots==1 else 's') + ' received from ' + application.dxCluster.getHost()
else:
msg = "No DX Cluster data from %s" % conf.dxClHost
if application.dxCluster:
msg = application.dxCluster.dxStatus()
self.MakeRow2("FFT number of errors", self.fft_error, msg)
else:
self.MakeRow2("FFT number of errors", self.fft_error)
@@ -3991,6 +3987,7 @@ class App(wx.App):
minh = 100
maxh = -1
self.main_frame.SetSizeHints(minw, minh, maxw, maxh)
self.main_frame.SetClientSize(wx.Size(width, self.height))
if hasattr(Hardware, 'pre_open'): # pre_open() is called before open()
Hardware.pre_open()
if self.local_conf.GetWidgets(self, Hardware, conf, frame, gbs, vertBox):
@@ -4030,7 +4027,6 @@ class App(wx.App):
if conf.dxClHost:
# create DX Cluster and register listener for change notification
self.dxCluster = dxcluster.DxCluster()
self.dxCluster.setListener(self.OnDxClChange)
self.dxCluster.start()
# Create shortcut keys for buttons
if conf.button_layout == 'Large screen':
@@ -4381,8 +4377,6 @@ class App(wx.App):
conf.tx_ip, conf.tx_audio_port,
conf.mic_sample_rate, conf.mic_channel_I, conf.mic_channel_Q,
0.7, conf.mic_playback_rate)
def OnDxClChange(self):
self.station_screen.Refresh()
def OnIdle(self, event):
if self.screen:
self.screen.OnIdle(event)
@@ -6639,6 +6633,9 @@ class App(wx.App):
self.ChangeDisplayFrequency(tune - vfo, vfo)
self.FldigiPoll()
self.HamlibPoll()
if self.dxCluster:
if self.dxCluster.Poll():
self.station_screen.Refresh()
if self.timer - self.slowheart_time0 > 0.5:
self.slowheart_time0 = self.timer
if self.w_phase:
+1 -1
View File
@@ -7,7 +7,7 @@ import os
# You must define the version here. A title string including
# the version will be written to __init__.py and read by quisk.py.
Version = '4.2.39'
Version = '4.2.41'
fp = open("__init__.py", "w") # write title string
fp.write("#Quisk version %s\n" % Version)