diff --git a/htdocs/index.html b/htdocs/index.html index 7659734e..83f689a7 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -45,10 +45,10 @@
-
+
-
+
diff --git a/htdocs/lib/DemodulatorPanel.js b/htdocs/lib/DemodulatorPanel.js index 35cea9c6..b6f460cc 100644 --- a/htdocs/lib/DemodulatorPanel.js +++ b/htdocs/lib/DemodulatorPanel.js @@ -217,6 +217,7 @@ DemodulatorPanel.prototype._apply = function(params) { this.getDemodulator().set_offset_frequency(params.offset_frequency); this.getDemodulator().setSquelch(params.squelch_level); this.updateButtons(); + this.setMagicKey(params.magic_key); }; DemodulatorPanel.prototype.setInitialParams = function(params) { @@ -227,6 +228,14 @@ DemodulatorPanel.prototype.resetInitialParams = function() { this.initialParams = {}; }; +DemodulatorPanel.prototype.setMagicKey = function(key) { + this.magic_key = key; +}; + +DemodulatorPanel.prototype.getMagicKey = function() { + return this.magic_key; +}; + DemodulatorPanel.prototype.onHashChange = function() { this._apply(this.transformHashParams(this.parseHash())); }; @@ -238,6 +247,7 @@ DemodulatorPanel.prototype.transformHashParams = function(params) { if (typeof(params.secondary_mod) !== 'undefined') ret.secondary_mod = params.secondary_mod; if (typeof(params.offset_frequency) !== 'undefined') ret.offset_frequency = params.offset_frequency; if (typeof(params.sql) !== 'undefined') ret.squelch_level = parseInt(params.sql); + if (typeof(params.key) !== 'undefined') ret.magic_key = params.key; return ret; }; @@ -340,10 +350,13 @@ DemodulatorPanel.prototype.updateHash = function() { freq: demod.get_offset_frequency() + self.center_freq, mod: demod.get_modulation(), secondary_mod: demod.get_secondary_demod(), - sql: demod.getSquelch() + sql: demod.getSquelch(), + key: self.magic_key }, function(value, key){ - if (typeof(value) === 'undefined' || value === false) return undefined; - return key + '=' + value; + if (typeof(value) === 'undefined' || value === false || value === '') + return undefined; + else + return key + '=' + value; }).filter(function(v) { return !!v; }).join(','); diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 7c2133b4..e5393032 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -151,6 +151,17 @@ function tuneBySteps(steps) { } } +function jumpBySteps(steps) { + steps = Math.round(steps); + if (steps != 0) { + var key = $('#openwebrx-panel-receiver').demodulatorPanel().getMagicKey(); + var f = center_freq + steps * bandwidth / 4; + ws.send(JSON.stringify({ + "type": "setfrequency", "params": { "frequency": f, "key": key } + })); + } +} + var waterfall_min_level; var waterfall_max_level; var waterfall_min_level_default; @@ -1080,6 +1091,11 @@ function on_ws_recv(evt) { $('#openwebrx-panel-receiver').css('border', x? '2px solid':''); } + if ('allow_audio_recording' in config) { + var x = config['allow_audio_recording']; + $('.openwebrx-record-button').css('display', x? '':'none'); + } + break; case "secondary_config": var s = json['value']; @@ -1149,11 +1165,11 @@ function on_ws_recv(evt) { case 'secondary_demod': var value = json['value']; var panels = [ - $("#openwebrx-panel-wsjt-message").wsjtMessagePanel(), - $('#openwebrx-panel-packet-message').packetMessagePanel(), - $('#openwebrx-panel-pocsag-message').pocsagMessagePanel(), - $('#openwebrx-panel-sstv-message').sstvMessagePanel(), - $('#openwebrx-panel-fax-message').faxMessagePanel(), + $("#openwebrx-panel-wsjt-message").wsjtMessagePanel(), + $('#openwebrx-panel-packet-message').packetMessagePanel(), + $('#openwebrx-panel-pocsag-message').pocsagMessagePanel(), + $('#openwebrx-panel-sstv-message').sstvMessagePanel(), + $('#openwebrx-panel-fax-message').faxMessagePanel(), $("#openwebrx-panel-js8-message").js8() ]; if (!panels.some(function(panel) { diff --git a/owrx/config/defaults.py b/owrx/config/defaults.py index 8b01bfde..6b71ce02 100644 --- a/owrx/config/defaults.py +++ b/owrx/config/defaults.py @@ -157,6 +157,9 @@ defaultConfig = PropertyLayer( waterfall_auto_min_range=50, ui_opacity=100, ui_frame=False, + magic_key="memagic", + allow_center_freq_changes=False, + allow_audio_recording=True, tuning_precision=2, squelch_auto_margin=10, google_maps_api_key="", diff --git a/owrx/connection.py b/owrx/connection.py index 13e66696..27635e98 100644 --- a/owrx/connection.py +++ b/owrx/connection.py @@ -139,6 +139,9 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): "tuning_precision", "ui_opacity", "ui_frame", + "allow_center_freq_changes", + "allow_audio_recording", + "magic_key", ] def __init__(self, conn): @@ -295,6 +298,15 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): profile = message["params"]["profile"].split("|") self.setSdr(profile[0]) self.sdr.activateProfile(profile[1]) + elif message["type"] == "setfrequency": + # If the magic key is set in the settings, only allow + # changes if it matches the received key + if "params" in message and self.stack["allow_center_freq_changes"]: + magic_key = self.stack["magic_key"] + params = message["params"] + if magic_key == "" or ("key" in params and params["key"] == magic_key): + if "frequency" in params: + self.sdr.setCenterFreq(params["frequency"]) elif message["type"] == "connectionproperties": if "params" in message: self.connectionProperties = message["params"] diff --git a/owrx/controllers/settings/general.py b/owrx/controllers/settings/general.py index 574ecefb..c4e46e82 100644 --- a/owrx/controllers/settings/general.py +++ b/owrx/controllers/settings/general.py @@ -91,6 +91,20 @@ class GeneralSettingsController(SettingsFormController): infotext="Specifies web page describing receiver usage policy " + "and shown when a client session times out.", ), + CheckboxInput( + "allow_audio_recording", + "Allow users to record received audio", + ), + CheckboxInput( + "allow_center_freq_changes", + "Allow users to change center frequency", + ), + TextInput( + "magic_key", + "Magic key", + infotext="Enter a key the user has to supply to change center frequency." + + " Leave empty if you do not want to protect frequency changes with a key.", + ), ), Section( "Receiver listings", diff --git a/owrx/source/__init__.py b/owrx/source/__init__.py index e3e79d9a..acc8bf92 100644 --- a/owrx/source/__init__.py +++ b/owrx/source/__init__.py @@ -116,21 +116,25 @@ class SdrSource(ABC): self.buffer = None self.props = PropertyStack() - - # layer 0 reserved for profile properties self.profileCarousel = SdrProfileCarousel(props) + + # layer 0 contains center_freq so that it can be changed + # independently of the profile + self.props.addLayer(0, PropertyLayer()) + + # layer 1 reserved for profile properties # prevent profile names from overriding the device name - self.props.addLayer(0, PropertyFilter(self.profileCarousel, ByLambda(lambda x: x != "name"))) + self.props.addLayer(1, PropertyFilter(self.profileCarousel, ByLambda(lambda x: x != "name"))) # props from our device config - self.props.addLayer(1, props) + self.props.addLayer(2, props) # the sdr_id is constant, so we put it in a separate layer # this is used to detect device changes, that are then sent to the client - self.props.addLayer(2, PropertyLayer(sdr_id=id).readonly()) + self.props.addLayer(3, PropertyLayer(sdr_id=id).readonly()) # finally, accept global config properties from the top-level config - self.props.addLayer(3, Config.get()) + self.props.addLayer(4, Config.get()) self.sdrProps = self.props.filter(*self.getEventNames()) @@ -228,9 +232,13 @@ class SdrSource(ABC): logger.debug("activating profile {0} for {1}".format(profile_id, self.getId())) try: self.profileCarousel.switch(profile_id) + self.setCenterFreq(self.profileCarousel["center_freq"]) except KeyError: logger.warning("invalid profile %s for sdr %s. ignoring", profile_id, self.getId()) + def setCenterFreq(self, frequency): + self.props["center_freq"] = frequency + def getId(self): return self.id