Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 227 additions & 1 deletion assets/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,96 @@
cursor: default;
}

/* Location Spoofing styles */
.location-row {
margin: 16px 0;
padding: 20px;
border-radius: 12px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
}

.location-label {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
color: var(--text-primary);
}

.location-inputs {
display: flex;
gap: 16px;
margin-bottom: 12px;
}

.location-field {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}

.location-field label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--text-secondary);
}

.location-input {
width: 100%;
padding: 10px 12px;
border-radius: 8px;
border: 1px solid var(--border-color);
background: var(--bg-tertiary);
color: var(--text-primary);
font-size: 13px;
font-family: 'Consolas', 'Courier New', monospace;
}

.location-input:focus {
outline: none;
border-color: var(--accent-color);
}

.location-input:disabled {
opacity: 0.5;
cursor: default;
}

.location-help {
font-size: 12px;
color: var(--text-tertiary);
}

.location-presets {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}

.location-preset {
padding: 6px 12px;
border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
color: var(--text-secondary);
font-size: 11px;
cursor: pointer;
transition: all 0.15s ease;
}

.location-preset:hover {
background: var(--bg-hover);
color: var(--text-primary);
}

.location-preset:disabled {
opacity: 0.5;
cursor: default;
}

/* Dark Theme Excluded Sites styles */
.dark-sites-row {
margin: 32px 0 24px;
Expand Down Expand Up @@ -560,6 +650,29 @@ <h2>DRM WebView</h2>
<textarea id="dark-sites-textarea" class="dark-sites-textarea" spellcheck="false" autocomplete="off" placeholder="Enter URL patterns (one per line)&#10;Example:&#10;*.example.com&#10;https://specific-site.com/*&#10;# Use # for comments"></textarea>
<div class="dark-sites-help">Enter URL patterns for sites where dark theme should be disabled (one per line). Supports wildcards (*) and comments starting with #. Browser internal pages (settings, history, etc.) are automatically excluded.</div>
</div>
<!-- Location Spoofing section -->
<div id="location-section" class="location-row">
<div class="location-label">Location Spoofing</div>
<div class="location-inputs">
<div class="location-field">
<label for="location-lat">Latitude</label>
<input id="location-lat" class="location-input" type="number" step="any" min="-90" max="90" placeholder="0.0" spellcheck="false" autocomplete="off" />
</div>
<div class="location-field">
<label for="location-lng">Longitude</label>
<input id="location-lng" class="location-input" type="number" step="any" min="-180" max="180" placeholder="0.0" spellcheck="false" autocomplete="off" />
</div>
</div>
<div class="location-help">Enter GPS coordinates to spoof. Enable "Spoof location" in Privacy settings above to activate. Latitude: -90 to 90, Longitude: -180 to 180.</div>
<div class="location-presets">
<button class="location-preset" data-lat="40.7128" data-lng="-74.0060">New York</button>
<button class="location-preset" data-lat="51.5074" data-lng="-0.1278">London</button>
<button class="location-preset" data-lat="35.6762" data-lng="139.6503">Tokyo</button>
<button class="location-preset" data-lat="48.8566" data-lng="2.3522">Paris</button>
<button class="location-preset" data-lat="-33.8688" data-lng="151.2093">Sydney</button>
<button class="location-preset" data-lat="37.7749" data-lng="-122.4194">San Francisco</button>
</div>
</div>
<!-- Custom User Agent section lives at the very bottom of settings -->
<div id="ua-section" class="ua-row">
<div class="ua-label">Target User Agent String</div>
Expand Down Expand Up @@ -611,7 +724,8 @@ <h2>DRM WebView</h2>
{ key: 'enable_javascript', name: 'Enable JavaScript', description: 'Allow JavaScript execution' },
{ key: 'enable_web_security', name: 'Web Security', description: 'Enforce same-origin policy' },
{ key: 'block_third_party_cookies', name: 'Block Third-Party Cookies', description: 'Prevent cross-site tracking' },
{ key: 'do_not_track', name: 'Do Not Track', description: 'Send DNT header' }
{ key: 'do_not_track', name: 'Do Not Track', description: 'Send DNT header' },
{ key: 'enable_location_spoofing', name: 'Location Spoofing', description: 'Override GPS coordinates sent to websites' }
]
},
drm: {
Expand Down Expand Up @@ -667,6 +781,8 @@ <h2>DRM WebView</h2>
let currentSettings = {}, originalSettings = {}, isDirty = false;
let targetUserAgent = '', originalTargetUserAgent = '';
let darkThemeExcludedSites = '', originalDarkThemeExcludedSites = '';
let spoofedLatitude = 0, originalSpoofedLatitude = 0;
let spoofedLongitude = 0, originalSpoofedLongitude = 0;
const drmElements = {};
let drmStatusSnapshot = null;

Expand Down Expand Up @@ -732,6 +848,21 @@ <h2>DRM WebView</h2>
darkThemeExcludedSites = '';
originalDarkThemeExcludedSites = '';
}
// Hydrate location spoofing coordinates from payload
if (typeof data.spoofed_latitude === 'number') {
spoofedLatitude = data.spoofed_latitude;
originalSpoofedLatitude = data.spoofed_latitude;
} else {
spoofedLatitude = 0;
originalSpoofedLatitude = 0;
}
if (typeof data.spoofed_longitude === 'number') {
spoofedLongitude = data.spoofed_longitude;
originalSpoofedLongitude = data.spoofed_longitude;
} else {
spoofedLongitude = 0;
originalSpoofedLongitude = 0;
}
if (data.meta && data.meta.storage_path) {
document.getElementById('storage-path').textContent = 'Storage: ' + data.meta.storage_path;
}
Expand All @@ -746,6 +877,7 @@ <h2>DRM WebView</h2>
renderSettings();
hydrateUserAgentEditor();
hydrateDarkThemeExcludedSitesEditor();
hydrateLocationEditor();
updateDirtyState();
loadDrmStatus(true);
}
Expand Down Expand Up @@ -842,6 +974,12 @@ <h2>DRM WebView</h2>
if (!isDirty && darkThemeExcludedSites !== originalDarkThemeExcludedSites) {
isDirty = true;
}
if (!isDirty && spoofedLatitude !== originalSpoofedLatitude) {
isDirty = true;
}
if (!isDirty && spoofedLongitude !== originalSpoofedLongitude) {
isDirty = true;
}
document.getElementById('btn-save').disabled = !isDirty;
}

Expand All @@ -852,6 +990,8 @@ <h2>DRM WebView</h2>
originalSettings = { ...currentSettings };
originalTargetUserAgent = targetUserAgent;
originalDarkThemeExcludedSites = darkThemeExcludedSites;
originalSpoofedLatitude = spoofedLatitude;
originalSpoofedLongitude = spoofedLongitude;
isDirty = false;
document.getElementById('btn-save').disabled = true;
log('Saved');
Expand Down Expand Up @@ -881,9 +1021,25 @@ <h2>DRM WebView</h2>
darkThemeExcludedSites = '';
originalDarkThemeExcludedSites = '';
}
// Reset location spoofing coordinates to defaults
if (typeof data.spoofed_latitude === 'number') {
spoofedLatitude = data.spoofed_latitude;
originalSpoofedLatitude = data.spoofed_latitude;
} else {
spoofedLatitude = 0;
originalSpoofedLatitude = 0;
}
if (typeof data.spoofed_longitude === 'number') {
spoofedLongitude = data.spoofed_longitude;
originalSpoofedLongitude = data.spoofed_longitude;
} else {
spoofedLongitude = 0;
originalSpoofedLongitude = 0;
}
renderSettings();
hydrateUserAgentEditor();
hydrateDarkThemeExcludedSitesEditor();
hydrateLocationEditor();
updateDirtyState();
loadDrmStatus(true);
log('Restored');
Expand Down Expand Up @@ -934,6 +1090,76 @@ <h2>DRM WebView</h2>
};
}

function hydrateLocationEditor() {
const latInput = document.getElementById('location-lat');
const lngInput = document.getElementById('location-lng');
const presetButtons = document.querySelectorAll('.location-preset');

if (latInput) {
latInput.value = spoofedLatitude || 0;
const locationEnabled = !!currentSettings.enable_location_spoofing;
latInput.disabled = !locationEnabled;
latInput.oninput = null;
latInput.oninput = () => {
let val = parseFloat(latInput.value) || 0;
val = Math.max(-90, Math.min(90, val));
spoofedLatitude = val;
if (typeof window.OnUpdateSetting === 'function') {
window.OnUpdateSetting('spoofed_latitude', val);
}
updateDirtyState();
const autoSave = !!currentSettings.auto_save_settings;
if (autoSave && typeof window.OnSaveSettings === 'function') {
window.OnSaveSettings();
}
};
}

if (lngInput) {
lngInput.value = spoofedLongitude || 0;
const locationEnabled = !!currentSettings.enable_location_spoofing;
lngInput.disabled = !locationEnabled;
lngInput.oninput = null;
lngInput.oninput = () => {
let val = parseFloat(lngInput.value) || 0;
val = Math.max(-180, Math.min(180, val));
spoofedLongitude = val;
if (typeof window.OnUpdateSetting === 'function') {
window.OnUpdateSetting('spoofed_longitude', val);
}
updateDirtyState();
const autoSave = !!currentSettings.auto_save_settings;
if (autoSave && typeof window.OnSaveSettings === 'function') {
window.OnSaveSettings();
}
};
}

// Handle preset buttons
presetButtons.forEach(btn => {
const locationEnabled = !!currentSettings.enable_location_spoofing;
btn.disabled = !locationEnabled;
btn.onclick = null;
btn.onclick = () => {
const lat = parseFloat(btn.dataset.lat) || 0;
const lng = parseFloat(btn.dataset.lng) || 0;
spoofedLatitude = lat;
spoofedLongitude = lng;
if (latInput) latInput.value = lat;
if (lngInput) lngInput.value = lng;
if (typeof window.OnUpdateSetting === 'function') {
window.OnUpdateSetting('spoofed_latitude', lat);
window.OnUpdateSetting('spoofed_longitude', lng);
}
updateDirtyState();
const autoSave = !!currentSettings.auto_save_settings;
if (autoSave && typeof window.OnSaveSettings === 'function') {
window.OnSaveSettings();
}
};
});
}

// Search helpers
function applySettingsSearch(term) {
window.__settingsSearchTerm = (term || '').toString();
Expand Down
38 changes: 38 additions & 0 deletions src/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,39 @@ namespace
}
return result;
}

double ParseDoubleLenient(const std::string &buffer, const std::string &key, double fallback)
{
if (key.empty())
return fallback;
std::string needle = std::string("\"") + key + "\"";
auto pos = buffer.find(needle);
if (pos == std::string::npos)
return fallback;
pos = buffer.find(':', pos + needle.size());
if (pos == std::string::npos)
return fallback;
++pos;
while (pos < buffer.size() && std::isspace(static_cast<unsigned char>(buffer[pos])))
++pos;
if (pos >= buffer.size())
return fallback;
// Parse number (may be negative, may have decimal point)
std::string numStr;
while (pos < buffer.size() && (buffer[pos] == '-' || buffer[pos] == '+' || buffer[pos] == '.' ||
std::isdigit(static_cast<unsigned char>(buffer[pos]))))
{
numStr += buffer[pos];
++pos;
}
if (numStr.empty())
return fallback;
try {
return std::stod(numStr);
} catch (...) {
return fallback;
}
}
}

void SettingsManager::EnsureDataDirectoryExists()
Expand Down Expand Up @@ -147,6 +180,9 @@ bool SettingsManager::LoadSettingsFromDisk(UI &ui)
// Parse string settings
ui.settings_.custom_user_agent = ParseStringLenient(content, "custom_user_agent", "");
ui.settings_.dark_theme_excluded_sites = ParseStringLenient(content, "dark_theme_excluded_sites", "");
// Parse location spoofing coordinates
ui.settings_.spoofed_latitude = ParseDoubleLenient(content, "spoofed_latitude", 0.0);
ui.settings_.spoofed_longitude = ParseDoubleLenient(content, "spoofed_longitude", 0.0);
ui.settings_storage_path_ = (migrated ? legacy_path.string() : primary_path.string());
}
else
Expand Down Expand Up @@ -182,6 +218,8 @@ bool SettingsManager::SaveSettingsToDisk(UI &ui)
doc << " \"values\": " << ui.BuildSettingsJSON() << ",\n";
doc << " \"custom_user_agent\": \"" << util::EscapeJsonString(ui.settings_.custom_user_agent) << "\",\n";
doc << " \"dark_theme_excluded_sites\": \"" << util::EscapeJsonString(ui.settings_.dark_theme_excluded_sites) << "\",\n";
doc << " \"spoofed_latitude\": " << ui.settings_.spoofed_latitude << ",\n";
doc << " \"spoofed_longitude\": " << ui.settings_.spoofed_longitude << ",\n";
doc << " \"meta\": {\n";
doc << " \"updated_at\": \"" << util::ToIso8601UTC(std::chrono::system_clock::now()) << "\",\n";
doc << " \"dirty\": false,\n";
Expand Down
Loading
Loading