Release Preview 20 shared desktop, Android and Sargo updates
Restore Gaia settings and media workflows, extend native network adapters, and align the APK payload. Generate French/English changelogs and website entries from the simplified JSON source. Validation: shared JS and Tundra unit tests; desktop workflows and persistence; signed APK payload parity; Sargo RAM and installed startup with boot readback. Android runtime qualification is recorded separately.
This commit is contained in:
parent
be2cdce823
commit
1311bed5e9
170 changed files with 19698 additions and 2495 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import {Input} from './services/input.mjs';
|
||||
import { Packages, packageURL } from './services/packages.mjs';
|
||||
import { Activities, isGaiaActivity } from './services/activities.mjs';
|
||||
import { authorizePlatform } from './services/platform.mjs';
|
||||
|
|
@ -10,6 +11,8 @@ import { Connections } from './Connections.mjs';
|
|||
import { Alarms } from './Alarms.mjs';
|
||||
const peers = new Set();
|
||||
let activities, packages;
|
||||
const input = new Input({identity:peer=>peer.app,broadcast});
|
||||
let editSequence=0;
|
||||
const nativePackages=(operation,data={})=>browser.runtime.sendNativeMessage('vulpes',{type:'packages.'+operation,...data}).then(JSON.parse);
|
||||
async function refreshInstalled(){const installed=await nativePackages('list');apps.splice(0,apps.length,...apps.filter(a=>!a.installed),...installed);}
|
||||
let apps, values, settings, dataStores, contacts, messages, connections, alarms, storage;
|
||||
|
|
@ -31,7 +34,7 @@ function grants(app) {
|
|||
function broadcast(type, data, target) {
|
||||
for (const peer of peers) {
|
||||
if (target && peer.app.id !== target) continue;
|
||||
if (type === 'settingchange' && data.key !== 'language.current' && !grants(peer.app).includes('settings.read')) continue;
|
||||
if (type === 'settingchange' && !['language.current', 'vulpes.theme'].includes(data.key) && !grants(peer.app).includes('settings.read')) continue;
|
||||
if (
|
||||
type === 'contactchange' &&
|
||||
!peer.app.manifest.permissions?.contacts?.access?.includes('read')
|
||||
|
|
@ -90,6 +93,7 @@ const ready = (async () => {
|
|||
await browser.storage.local.set({ settings: values });
|
||||
}
|
||||
activities = new Activities({identity,apps:()=>apps,notify,uuid:()=>crypto.randomUUID(),
|
||||
choose:data=>broadcast('activity-choice',data,'system'),
|
||||
launch:(target,path,{reload=false,entryPoint}={})=>launch(target,entryPoint,path,reload)});
|
||||
let writes = Promise.resolve();
|
||||
settings = new SettingsService(
|
||||
|
|
@ -100,7 +104,12 @@ const ready = (async () => {
|
|||
const next = { ...values, ...Object.fromEntries(entries) };
|
||||
await browser.storage.local.set({ settings: next });
|
||||
values = next;
|
||||
for (const [key, value] of entries) broadcast('settingchange', { key, value });
|
||||
for (const [key, value] of entries) {
|
||||
broadcast('settingchange', { key, value });
|
||||
if(key==='keyboard.gaia.enabled') {
|
||||
input.hide();broadcast('input-mode',{enabled:value===true});
|
||||
}
|
||||
}
|
||||
});
|
||||
writes = write.catch(() => {});
|
||||
return write;
|
||||
|
|
@ -302,6 +311,9 @@ async function handle(peer, request) {
|
|||
}
|
||||
if (!Object.keys(params.values).length) return null;
|
||||
}
|
||||
if (method === 'input.config') return values['keyboard.gaia.enabled'] === true;
|
||||
if (method === 'input.request') return input.request(peer,params);
|
||||
if (method === 'appearance.current') return values['vulpes.theme'] || 'light';
|
||||
if (method === 'locale.current') return values['language.current'] || 'en-US';
|
||||
if (method === 'settings.snapshot') {
|
||||
if (!grants(app).includes('settings.read')) throw Error('PERMISSION_DENIED');
|
||||
|
|
@ -357,6 +369,7 @@ async function handle(peer, request) {
|
|||
return null;
|
||||
}
|
||||
if (method === 'activities.cancel') return activities.cancel(peer);
|
||||
if (method === 'activities.select') return activities.select(peer,params);
|
||||
if (method === 'activities.subscribe') return activities.subscribe(peer);
|
||||
if (method === 'activities.finish') return activities.finish(peer,params);
|
||||
if (method === 'activities.start' && isGaiaActivity(params)) return activities.start(peer,params);
|
||||
|
|
@ -412,6 +425,7 @@ async function handle(peer, request) {
|
|||
return null;
|
||||
}
|
||||
if (method === 'desktop.wakelock') return null; // Android Activity owns the visible screen.
|
||||
if (method === 'storage.stats') {if(app.id!=='settings')throw Error('PERMISSION_DENIED');return android('storage',{});}
|
||||
if (method === 'media.request') return media(app, params);
|
||||
throw Error('METHOD_NOT_SUPPORTED');
|
||||
}
|
||||
|
|
@ -449,6 +463,15 @@ browser.runtime.onConnect.addListener((port) => {
|
|||
app,
|
||||
manager: { documentURI: { spec: url.href } },
|
||||
sendAsyncMessage: (_, event) => notify(peer, event.type, event.data),
|
||||
edits: new Map(),
|
||||
sendQuery(_, params) {
|
||||
const id=++editSequence;
|
||||
return new Promise((resolve,reject)=>{
|
||||
const timer=setTimeout(()=>{peer.edits.delete(id);reject(Error('INPUT_CONTEXT_EXPIRED'));},5000);
|
||||
peer.edits.set(id,{resolve,reject,timer});
|
||||
port.postMessage({edit:{id,params}});
|
||||
});
|
||||
},
|
||||
};
|
||||
peers.add(peer);
|
||||
});
|
||||
|
|
@ -456,6 +479,16 @@ browser.runtime.onConnect.addListener((port) => {
|
|||
let reply;
|
||||
try {
|
||||
await attached;
|
||||
if(Object.hasOwn(message,'inputFocus')) {
|
||||
if(values['keyboard.gaia.enabled']===true) input.changed(peer,message.inputFocus);
|
||||
return;
|
||||
}
|
||||
if(Object.hasOwn(message,'editReply')) {
|
||||
const edit=peer.edits.get(message.editReply);
|
||||
if(edit) {clearTimeout(edit.timer);peer.edits.delete(message.editReply);
|
||||
if(message.error)edit.reject(Error(message.error));else edit.resolve(message.result);}
|
||||
return;
|
||||
}
|
||||
reply = { result: await handle(peer, message.request) };
|
||||
} catch (e) {
|
||||
reply = { error: { code: e.message || 'HOST_ERROR' } };
|
||||
|
|
@ -469,6 +502,9 @@ browser.runtime.onConnect.addListener((port) => {
|
|||
attached
|
||||
.then(() => {
|
||||
peers.delete(peer);
|
||||
input.cleanup(peer);
|
||||
for(const edit of peer.edits.values()) {clearTimeout(edit.timer);edit.reject(Error('INPUT_CONTEXT_EXPIRED'));}
|
||||
peer.edits.clear();
|
||||
if (peer.app.id === 'system' && ![...peers].some((p) => p.app.id === 'system'))
|
||||
systemReady = false;
|
||||
activities.cleanup(peer);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,23 @@
|
|||
});
|
||||
}
|
||||
function onMessage(message) {
|
||||
if (message.edit) {
|
||||
try {
|
||||
const result=editor.edit(message.edit.params);
|
||||
result.inputMode=originalModes.get(editor.inputElement) ||
|
||||
(editor.inputElement.classList.contains('recipient')?'tel':'');
|
||||
port.postMessage({editReply:message.edit.id, result});
|
||||
}
|
||||
catch (error) { port.postMessage({editReply:message.edit.id, error:String(error.message)}); }
|
||||
return;
|
||||
}
|
||||
if (message.event) {
|
||||
if(message.event.type==='input-mode') {
|
||||
gaiaKeyboard=message.event.data.enabled===true;
|
||||
if(!gaiaKeyboard) restoreMode();
|
||||
win.VulpesNativeInput=gaiaKeyboard;
|
||||
win.dispatchEvent(new window.Event('vulpes-input-configured'));
|
||||
}
|
||||
win.dispatchEvent(
|
||||
new window.CustomEvent('vulpes-service-event', { detail: cloneInto(message.event, win) }),
|
||||
);
|
||||
|
|
@ -50,6 +66,45 @@
|
|||
}, 1000);
|
||||
}
|
||||
connect();
|
||||
const editor = new InputEditor(window);
|
||||
let gaiaKeyboard = false;
|
||||
const originalModes = new WeakMap();
|
||||
function restoreMode() {
|
||||
const field = editor.inputElement;
|
||||
if (!field || !originalModes.has(field)) return;
|
||||
const value = originalModes.get(field);
|
||||
if (value === null) field.removeAttribute('inputmode');
|
||||
else field.setAttribute('inputmode', value);
|
||||
originalModes.delete(field);
|
||||
}
|
||||
request({version:1,id:0,method:'input.config',params:{}}).then(reply => {
|
||||
gaiaKeyboard = reply.result === true;
|
||||
win.VulpesNativeInput = gaiaKeyboard;
|
||||
win.dispatchEvent(new window.Event('vulpes-input-configured'));
|
||||
}).catch(error => console.error('Gaia input configuration:', error));
|
||||
for (const type of ['focusin', 'pointerdown', 'click']) addEventListener(type, event => {
|
||||
if (!gaiaKeyboard || editor.editing || location.hostname === 'keyboard.localhost') return;
|
||||
let element = event.target;
|
||||
if (element?.isContentEditable) element=element.closest('[contenteditable=true]') || element;
|
||||
const editable = element?.isContentEditable ||
|
||||
(['input','textarea'].includes(element?.localName) &&
|
||||
!['button','submit','range','checkbox','radio','date','time','datetime-local','file'].includes(element.type));
|
||||
if (!editable || element.disabled || element.readOnly) {
|
||||
if (type === 'click' && !element?.closest?.('[data-vulpes-keep-input]')) port.postMessage({inputFocus:null});
|
||||
return;
|
||||
}
|
||||
if (element !== editor.inputElement) restoreMode();
|
||||
editor.focus(element);
|
||||
if (!originalModes.has(element)) originalModes.set(element, element.getAttribute('inputmode'));
|
||||
// Suppress Android's IME only in this editor; external web pages keep it.
|
||||
element.setAttribute('inputmode', 'none');
|
||||
const state=editor.state();
|
||||
state.inputMode=originalModes.get(element) || (element.classList.contains('recipient')?'tel':'');
|
||||
port.postMessage({inputFocus:state});
|
||||
}, true);
|
||||
addEventListener('visibilitychange', () => {
|
||||
if (document.hidden && gaiaKeyboard) { restoreMode(); port.postMessage({inputFocus:null}); }
|
||||
});
|
||||
addEventListener('unload', () => clearTimeout(reconnect));
|
||||
exportFunction(request, win.navigator, { defineAs: 'vulpesRequest' });
|
||||
const frames = new Map();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"http://*.localhost/*"
|
||||
],
|
||||
"js": [
|
||||
"input-editor.js",
|
||||
"content.js"
|
||||
],
|
||||
"all_frames": true,
|
||||
|
|
|
|||
Loading…
Reference in a new issue