Committing updates and pushed version

This commit is contained in:
WolverinDEV 2020-10-05 15:10:49 +02:00
parent 7087514df1
commit a636515de8
17 changed files with 416 additions and 331 deletions

2
github

@ -1 +1 @@
Subproject commit 9c3cc6d05838a03a5827836b300f8bc8e71b26d2
Subproject commit 7c087d46ad75ff641d5862a57ff13f3e860cc8a4

View File

@ -122,5 +122,5 @@ function deploy_client() {
#install_npm
#compile_scripts
#compile_native
package_client
#deploy_client
#package_client
deploy_client

View File

@ -10,6 +10,7 @@ import * as loader from "./../ui-loader";
import * as url from "url";
import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window";
import {referenceApp, dereferenceApp} from "../AppInstance";
import {closeURLPreview, openURLPreview} from "../url-preview";
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
@ -49,7 +50,7 @@ function spawnMainWindow(rendererEntryPoint: string) {
mainWindow.on('closed', () => {
app.releaseSingleInstanceLock();
require("../url-preview").close();
closeURLPreview();
mainWindow = null;
dereferenceApp();
@ -94,8 +95,7 @@ function spawnMainWindow(rendererEntryPoint: string) {
}
console.log("Got new window " + frameName);
const url_preview = require("./url-preview");
url_preview.open_preview(url_str);
openURLPreview(url_str).then(() => {});
} catch(error) {
console.error("Failed to open preview window for URL %s: %o", url_str, error);
dialog.showErrorBox("Failed to open preview", "Failed to open preview URL: " + url_str + "\nError: " + error);

View File

@ -4,7 +4,7 @@ import {ProxiedClass} from "../../shared/proxy/Definitions";
import {BrowserWindow, dialog} from "electron";
import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window";
import {Arguments, processArguments} from "../../shared/process-arguments";
import {open_preview} from "../url-preview";
import {openURLPreview} from "../url-preview";
import * as path from "path";
class ProxyImplementation extends ProxiedClass<ExternalModal> implements ExternalModal {
@ -71,15 +71,16 @@ class ProxyImplementation extends ProxiedClass<ExternalModal> implements Externa
}
}
open_preview(url.toString());
openURLPreview(url.toString());
} catch(error) {
console.error("Failed to open preview window for URL %s: %o", url_str, error);
dialog.showErrorBox("Failed to open preview", "Failed to open preview URL: " + url_str + "\nError: " + error);
}
});
if(processArguments.has_flag(Arguments.DEV_TOOLS))
if(processArguments.has_flag(Arguments.DEV_TOOLS)) {
this.windowInstance.webContents.openDevTools();
}
try {
await this.windowInstance.loadURL(url);

View File

@ -1,34 +1,26 @@
import * as electron from "electron";
import ipcMain = electron.ipcMain;
import BrowserWindow = electron.BrowserWindow;
import {NativeMenuBarEntry} from "../../shared/MenuBarDefinitions";
import {Menu, MenuItemConstructorOptions} from "electron";
ipcMain.on('top-menu', (event, menu_template: electron.MenuItemConstructorOptions[]) => {
ipcMain.on("menu-bar", (event, menuBar: NativeMenuBarEntry[]) => {
const window = BrowserWindow.fromWebContents(event.sender);
const process_template = (item: electron.MenuItemConstructorOptions) => {
if(typeof(item.icon) === "string" && item.icon.startsWith("data:"))
item.icon = electron.nativeImage.createFromDataURL(item.icon);
item.click = () => window.webContents.send('top-menu', item.id);
for(const i of item.submenu as electron.MenuItemConstructorOptions[] || []) {
process_template(i);
}
};
for(const m of menu_template)
process_template(m);
try {
const menu = new electron.Menu();
for(const m of menu_template) {
try {
menu.append(new electron.MenuItem(m));
} catch(error) {
console.error("Failed to build menu entry: %o\nSource: %o", error, m);
const processEntry = (entry: NativeMenuBarEntry): MenuItemConstructorOptions => {
return {
type: entry.type === "separator" ? "separator" : entry.children?.length ? "submenu" : "normal",
label: entry.label,
icon: entry.icon ? electron.nativeImage.createFromDataURL(entry.icon).resize({ height: 16, width: 16 }) : undefined,
enabled: !entry.disabled,
click: entry.uniqueId && (() => event.sender.send("menu-bar", "item-click", entry.uniqueId)),
submenu: entry.children?.map(processEntry)
}
}
window.setMenu(menu_template.length == 0 ? undefined : menu);
} catch(error) {
console.error("Failed to set window menu: %o", error);
};
window.setMenu(Menu.buildFromTemplate(menuBar.map(processEntry)));
} catch (error) {
console.error("failed to set menu bar for %s: %o", window.getTitle(), error);
}
});

View File

@ -5,7 +5,7 @@ import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window";
let global_window: electron.BrowserWindow;
let global_window_promise: Promise<void>;
export async function close() {
export async function closeURLPreview() {
while(global_window_promise) {
try {
await global_window_promise;
@ -20,13 +20,14 @@ export async function close() {
}
}
export async function open_preview(url: string) {
export async function openURLPreview(url: string) {
while(global_window_promise) {
try {
await global_window_promise;
break;
} catch(error) {} /* error will be already logged */
}
if(!global_window) {
global_window_promise = (async () => {
global_window = new electron.BrowserWindow({

View File

@ -1,64 +1,123 @@
import {ContextMenuEntry, ContextMenuFactory, setGlobalContextMenuFactory} from "tc-shared/ui/ContextMenu";
import * as electron from "electron";
import {MenuItemConstructorOptions} from "electron";
import {clientIconClassToImage} from "./IconHelper";
import {clientIconClassToImage, remoteIconDatafier, RemoteIconWrapper} from "./IconHelper";
import {getIconManager, RemoteIconInfo} from "tc-shared/file/Icons";
const {Menu} = electron.remote;
let currentMenu: electron.Menu;
let currentMenu: ContextMenuInstance;
class ContextMenuInstance {
private readonly closeCallback: () => void | undefined;
private readonly menuOptions: MenuItemConstructorOptions[];
private currentMenu: electron.Menu;
function mapMenuEntry(entry: ContextMenuEntry) : MenuItemConstructorOptions {
switch (entry.type) {
case "normal":
return {
type: "normal",
label: typeof entry.label === "string" ? entry.label : entry.label.text,
enabled: entry.enabled,
visible: entry.visible,
click: entry.click,
icon: typeof entry.icon === "string" ? clientIconClassToImage(entry.icon) : undefined,
id: entry.uniqueId,
submenu: entry.subMenu ? entry.subMenu.map(mapMenuEntry).filter(e => !!e) : undefined
};
private wrappedIcons: RemoteIconWrapper[] = [];
private wrappedIconListeners: (() => void)[] = [];
case "checkbox":
return {
type: "normal",
label: typeof entry.label === "string" ? entry.label : entry.label.text,
enabled: entry.enabled,
visible: entry.visible,
click: entry.click,
id: entry.uniqueId,
constructor(entries: ContextMenuEntry[], closeCallback: () => void | undefined) {
this.closeCallback = closeCallback;
this.menuOptions = entries.map(e => this.wrapEntry(e)).filter(e => !!e);
}
checked: entry.checked
};
destroy() {
this.currentMenu?.closePopup();
this.currentMenu = undefined;
case "separator":
return {
type: "separator"
};
this.wrappedIconListeners.forEach(callback => callback());
this.wrappedIcons.forEach(icon => remoteIconDatafier.unrefIcon(icon));
default:
return undefined;
this.wrappedIcons = [];
this.wrappedIconListeners = [];
}
spawn(pageX: number, pageY: number) {
this.currentMenu = Menu.buildFromTemplate(this.menuOptions);
this.currentMenu.popup({
callback: () => {
if(this.closeCallback) {
this.closeCallback();
}
currentMenu = undefined;
},
x: pageX,
y: pageY,
window: electron.remote.BrowserWindow.getFocusedWindow()
});
}
private wrapEntry(entry: ContextMenuEntry) : MenuItemConstructorOptions {
if(typeof entry.visible === "boolean" && !entry.visible) { return undefined; }
let options: MenuItemConstructorOptions;
let icon: string | RemoteIconInfo | undefined;
switch (entry.type) {
case "normal":
icon = entry.icon;
options = {
type: entry.subMenu?.length ? "submenu" : "normal",
label: typeof entry.label === "string" ? entry.label : entry.label.text,
enabled: entry.enabled,
click: entry.click,
id: entry.uniqueId,
submenu: entry.subMenu?.length ? entry.subMenu.map(e => this.wrapEntry(e)).filter(e => !!e) : undefined
};
break;
case "checkbox":
icon = entry.icon;
options = {
type: "checkbox",
label: typeof entry.label === "string" ? entry.label : entry.label.text,
enabled: entry.enabled,
click: entry.click,
id: entry.uniqueId,
checked: entry.checked
};
break;
case "separator":
return {
type: "separator"
};
default:
return undefined;
}
if(typeof icon === "object") {
const remoteIcon = getIconManager().resolveIcon(icon.iconId, icon.serverUniqueId, icon.handlerId);
const wrapped = remoteIconDatafier.resolveIcon(remoteIcon);
remoteIconDatafier.unrefIcon(wrapped);
/*
// Sadly we can't update the icon on the fly, so we've to live with whatever we have
this.wrappedIcons.push(wrapped);
this.wrappedIconListeners.push(wrapped.onDataUrlChange(dataUrl => {
options.icon = electron.nativeImage.createFromDataURL(dataUrl);
}));
*/
if(wrapped.getDataUrl()) {
options.icon = electron.remote.nativeImage.createFromDataURL(wrapped.getDataUrl());
}
} else if(typeof icon === "string") {
options.icon = clientIconClassToImage(icon);
}
return options;
}
}
setGlobalContextMenuFactory(new class implements ContextMenuFactory {
closeContextMenu() {
currentMenu?.closePopup();
currentMenu?.destroy();
currentMenu = undefined;
}
spawnContextMenu(position: { pageX: number; pageY: number }, entries: ContextMenuEntry[], callbackClose?: () => void) {
this.closeContextMenu();
currentMenu = Menu.buildFromTemplate(entries.map(mapMenuEntry).filter(e => !!e));
currentMenu.popup({
callback: () => {
callbackClose();
currentMenu = undefined;
},
x: position.pageX,
y: position.pageY,
window: electron.remote.BrowserWindow.getFocusedWindow()
});
currentMenu = new ContextMenuInstance(entries, callbackClose);
currentMenu.spawn(position.pageX, position.pageY);
}
});

View File

@ -6,9 +6,10 @@ import {
spriteUrl as kClientSpriteUrl,
spriteWidth as kClientSpriteWidth,
spriteHeight as kClientSpriteHeight,
spriteEntries as kClientSpriteEntries
spriteEntries as kClientSpriteEntries, ClientIcon
} from "svg-sprites/client-icons";
import {NativeImage} from "electron";
import {RemoteIcon} from "tc-shared/file/Icons";
let nativeSprite: NativeImage;
@ -24,6 +25,134 @@ export function clientIconClassToImage(klass: string) : NativeImage {
});
}
export class RemoteIconDatafier {
private cachedIcons: {[key: string]:{ refCount: number, icon: RemoteIconWrapper }} = {};
private cleanupTimer;
constructor() { }
destroy() {
clearTimeout(this.cleanupTimer);
}
resolveIcon(icon: RemoteIcon) : RemoteIconWrapper {
const uniqueId = icon.iconId + "-" + icon.serverUniqueId;
if(!this.cachedIcons[uniqueId]) {
this.cachedIcons[uniqueId] = {
refCount: 0,
icon: new RemoteIconWrapper(uniqueId, icon)
}
}
const cache = this.cachedIcons[uniqueId];
cache.refCount++;
return cache.icon;
}
unrefIcon(icon: RemoteIconWrapper) {
const cache = this.cachedIcons[icon.uniqueId];
if(!cache) { return; }
cache.refCount--;
if(cache.refCount <= 0) {
if(this.cleanupTimer) {
clearTimeout(this.cleanupTimer);
}
this.cleanupTimer = setTimeout(() => this.cleanupIcons(), 10 * 1000);
}
}
private cleanupIcons() {
this.cleanupTimer = undefined;
for(const key of Object.keys(this.cachedIcons)) {
if(this.cachedIcons[key].refCount <= 0) {
this.cachedIcons[key].icon.destroy();
delete this.cachedIcons[key];
}
}
}
}
export const remoteIconDatafier = new RemoteIconDatafier();
export class RemoteIconWrapper {
readonly callbackUpdated: ((newUrl: string) => void)[] = [];
readonly uniqueId: string;
private readonly icon: RemoteIcon;
private readonly callbackStateChanged: () => void;
private dataUrl: string | undefined;
private currentImageUrl: string;
constructor(uniqueId: string, icon: RemoteIcon) {
this.icon = icon;
this.uniqueId = uniqueId;
this.callbackStateChanged = this.handleIconStateChanged.bind(this);
this.icon.events.on("notify_state_changed", this.callbackStateChanged);
this.handleIconStateChanged();
}
destroy() {
this.icon.events.off("notify_state_changed", this.callbackStateChanged);
this.currentImageUrl = undefined;
}
getDataUrl() : string | undefined { return this.dataUrl; }
onDataUrlChange(callback: (newUrl: string) => void) : () => void {
this.callbackUpdated.push(callback);
return () => {
const index = this.callbackUpdated.indexOf(callback);
if(index !== -1) { this.callbackUpdated.splice(index, 1); }
}
}
private handleIconStateChanged() {
if(this.icon.getState() === "loaded") {
const imageUrl = this.icon.getImageUrl();
this.currentImageUrl = this.icon.getImageUrl();
const image = new Image();
image.src = imageUrl;
new Promise((resolve, reject) => {
image.onload = resolve;
image.onerror = reject;
}).then(() => {
if(this.currentImageUrl !== imageUrl) { return; }
const canvas = document.createElement("canvas");
if(image.naturalWidth > 1000 || image.naturalHeight > 1000) {
throw "image dimensions are too large";
} else {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
}
canvas.getContext("2d").drawImage(image, 0, 0);
this.setDataUrl(imageUrl, canvas.toDataURL());
}).catch(() => {
this.setDataUrl(imageUrl, clientIconClassToImage(ClientIcon.Error).toDataURL());
});
} else if(this.icon.getState() === "error") {
this.setDataUrl(undefined, clientIconClassToImage(ClientIcon.Error).toDataURL());
} else {
this.setDataUrl(undefined, undefined);
}
}
private setDataUrl(sourceImageUrl: string | undefined, dataUrl: string) {
if(sourceImageUrl && this.currentImageUrl !== sourceImageUrl) { return; }
this.currentImageUrl = undefined; /* no image is loading any more */
if(this.dataUrl === dataUrl) { return; }
this.dataUrl = dataUrl;
this.callbackUpdated.forEach(callback => callback(dataUrl));
}
}
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
priority: 100,
name: "native icon sprite loader",
@ -40,7 +169,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
canvas.height = kClientSpriteHeight;
canvas.getContext("2d").drawImage(image, 0, 0);
nativeSprite = electron.remote.nativeImage.createFromDataURL( canvas.toDataURL());
nativeSprite = electron.remote.nativeImage.createFromDataURL(canvas.toDataURL());
}
})

View File

@ -0,0 +1,99 @@
import * as electron from "electron";
import {MenuBarDriver, MenuBarEntry} from "tc-shared/ui/frames/menu-bar";
import {IpcRendererEvent} from "electron";
import {getIconManager} from "tc-shared/file/Icons";
import {clientIconClassToImage, remoteIconDatafier, RemoteIconWrapper} from "./IconHelper";
import {NativeMenuBarEntry} from "../shared/MenuBarDefinitions";
let uniqueEntryIdIndex = 0;
export class NativeMenuBarDriver implements MenuBarDriver {
private readonly ipcChannelListener;
private menuEntries: NativeMenuBarEntry[] = [];
private remoteIconReferences: RemoteIconWrapper[] = [];
private remoteIconListeners: (() => void)[] = [];
private callbacks: {[key: string]: () => void} = {};
constructor() {
this.ipcChannelListener = this.handleMenuBarEvent.bind(this);
electron.ipcRenderer.on("menu-bar", this.ipcChannelListener);
}
destroy() {
electron.ipcRenderer.off("menu-bar", this.ipcChannelListener);
this.internalClearEntries();
}
private internalClearEntries() {
this.callbacks = {};
this.menuEntries = [];
this.remoteIconListeners.forEach(callback => callback());
this.remoteIconListeners = [];
this.remoteIconReferences.forEach(icon => remoteIconDatafier.unrefIcon(icon));
this.remoteIconReferences = [];
}
clearEntries() {
this.internalClearEntries()
electron.ipcRenderer.send("menu-bar", []);
}
setEntries(entries: MenuBarEntry[]) {
this.internalClearEntries();
this.menuEntries = entries.map(e => this.wrapEntry(e)).filter(e => !!e);
electron.ipcRenderer.send("menu-bar", this.menuEntries);
}
private wrapEntry(entry: MenuBarEntry) : NativeMenuBarEntry {
if(entry.type === "separator") {
return { type: "separator", uniqueId: entry.uniqueId || "item-" + (++uniqueEntryIdIndex) };
} else if(entry.type === "normal") {
if(typeof entry.visible === "boolean" && !entry.visible) {
return null;
}
let result = {
type: "normal",
uniqueId: entry.uniqueId || "item-" + (++uniqueEntryIdIndex),
label: entry.label,
disabled: entry.disabled,
children: entry.children?.map(e => this.wrapEntry(e)).filter(e => !!e)
} as NativeMenuBarEntry;
if(entry.click) {
this.callbacks[result.uniqueId] = entry.click;
}
if(typeof entry.icon === "object") {
/* we've a remote icon */
const remoteIcon = getIconManager().resolveIcon(entry.icon.iconId, entry.icon.serverUniqueId, entry.icon.handlerId);
const wrapped = remoteIconDatafier.resolveIcon(remoteIcon);
this.remoteIconReferences.push(wrapped);
wrapped.onDataUrlChange(url => {
result.icon = url;
electron.ipcRenderer.send("menu-bar", this.menuEntries);
});
result.icon = wrapped.getDataUrl();
} else if(typeof entry.icon === "string") {
result.icon = clientIconClassToImage(entry.icon).toDataURL();
}
return result;
} else {
return undefined;
}
}
private handleMenuBarEvent(_event: IpcRendererEvent, eventType: string, ...args) {
if(eventType === "item-click") {
const callback = this.callbacks[args[0]];
if(typeof callback === "function") {
callback();
}
}
}
}

View File

@ -1,246 +0,0 @@
import {clientIconClassToImage} from "./IconHelper";
import * as electron from "electron";
import * as mbar from "tc-shared/ui/frames/MenuBar";
import {Arguments, processArguments} from "../shared/process-arguments";
import ipcRenderer = electron.ipcRenderer;
import {LocalIcon} from "tc-shared/file/Icons";
namespace native {
import ipcRenderer = electron.ipcRenderer;
let _item_index = 1;
abstract class NativeMenuBase {
protected _handle: NativeMenuBar;
protected _click: () => any;
id: string;
protected constructor(handle: NativeMenuBar, id?: string) {
this._handle = handle;
this.id = id || ("item_" + (_item_index++));
}
abstract build() : electron.MenuItemConstructorOptions;
abstract items(): (mbar.MenuItem | mbar.HRItem)[];
trigger_click() {
if(this._click)
this._click();
}
}
class NativeMenuItem extends NativeMenuBase implements mbar.MenuItem {
private _items: (NativeMenuItem | NativeHrItem)[] = [];
private _label: string;
private _enabled: boolean = true;
private _visible: boolean = true;
private _icon_data: string;
constructor(handle: NativeMenuBar) {
super(handle);
}
append_hr(): mbar.HRItem {
const item = new NativeHrItem(this._handle);
this._items.push(item);
return item;
}
append_item(label: string): mbar.MenuItem {
const item = new NativeMenuItem(this._handle);
item.label(label);
this._items.push(item);
return item;
}
click(callback: () => any): this {
this._click = callback;
return this;
}
delete_item(item: mbar.MenuItem | mbar.HRItem) {
const i_index = this._items.indexOf(item as any);
if(i_index < 0) return;
this._items.splice(i_index, 1);
}
disabled(value?: boolean): boolean {
if(typeof(value) === "boolean")
this._enabled = !value;
return !this._enabled;
}
icon(klass?: string | Promise<LocalIcon> | LocalIcon): string {
if(typeof(klass) === "string") {
const buffer = clientIconClassToImage(klass);
if(buffer)
this._icon_data = buffer.toDataURL();
}
return "";
}
items(): (mbar.MenuItem | mbar.HRItem)[] {
return this._items;
}
label(value?: string): string {
if(typeof(value) === "string")
this._label = value;
return this._label;
}
visible(value?: boolean): boolean {
if(typeof(value) === "boolean")
this._visible = value;
return this._visible;
}
build(): Electron.MenuItemConstructorOptions {
return {
id: this.id,
label: this._label || "",
submenu: this._items.length > 0 ? this._items.map(e => e.build()) : undefined,
enabled: this._enabled,
visible: this._visible,
icon: this._icon_data
}
}
}
class NativeHrItem extends NativeMenuBase implements mbar.HRItem {
constructor(handle: NativeMenuBar) {
super(handle);
}
build(): Electron.MenuItemConstructorOptions {
return {
type: 'separator',
id: this.id
}
}
items(): (mbar.MenuItem | mbar.HRItem)[] {
return [];
}
}
function is_similar_deep(a, b) {
if(typeof(a) !== typeof(b))
return false;
if(typeof(a) !== "object")
return a === b;
const aProps = Object.keys(a);
const bProps = Object.keys(b);
if (aProps.length != bProps.length)
return false;
for (let i = 0; i < aProps.length; i++) {
const propName = aProps[i];
if(!is_similar_deep(a[propName], b[propName]))
return false;
}
return true;
}
export class NativeMenuBar implements mbar.MenuBarDriver {
private static _instance: NativeMenuBar;
private menu: electron.Menu;
private _items: NativeMenuItem[] = [];
private _current_menu: electron.MenuItemConstructorOptions[];
public static instance() : NativeMenuBar {
if(!this._instance)
this._instance = new NativeMenuBar();
return this._instance;
}
append_item(label: string): mbar.MenuItem {
const item = new NativeMenuItem(this);
item.label(label);
this._items.push(item);
return item;
}
delete_item(item: mbar.MenuItem) {
const i_index = this._items.indexOf(item as any);
if(i_index < 0) return;
this._items.splice(i_index, 1);
}
flush_changes() {
const target_menu = this.build_menu();
if(is_similar_deep(target_menu, this._current_menu))
return;
this._current_menu = target_menu;
ipcRenderer.send('top-menu', target_menu);
}
private build_menu() : electron.MenuItemConstructorOptions[] {
return this._items.map(e => e.build());
}
items(): mbar.MenuItem[] {
return this._items;
}
initialize() {
this.menu = new electron.remote.Menu();
ipcRenderer.on('top-menu', (event, clicked_item) => {
console.log("Item %o clicked", clicked_item);
const check_item = (item: NativeMenuBase) => {
if(item.id == clicked_item) {
item.trigger_click();
return true;
}
for(const child of item.items())
if(check_item(child as NativeMenuBase))
return true;
};
for(const item of this._items)
if(check_item(item))
return;
});
}
}
}
mbar.set_driver(native.NativeMenuBar.instance());
// @ts-ignore
mbar.native_actions = {
open_change_log() {
call_basic_action("open-changelog");
},
check_native_update() {
call_basic_action("check-native-update");
},
quit() {
call_basic_action("quit");
},
open_dev_tools() {
call_basic_action("open-dev-tools");
},
reload_page() {
call_basic_action("reload-window")
},
show_dev_tools() { return processArguments.has_flag(Arguments.DEV_TOOLS); }
};
const call_basic_action = (name: string, ...args: any[]) => ipcRenderer.send('basic-action', name, ...args);

View File

@ -16,6 +16,7 @@ import {NativeFilter, NStateFilter, NThresholdFilter, NVoiceLevelFilter} from ".
import {IDevice} from "tc-shared/audio/recorder";
import {LogCategory, logWarn} from "tc-shared/log";
import NativeFilterMode = audio.record.FilterMode;
import {Settings, settings} from "tc-shared/settings";
export class NativeInput implements AbstractInput {
static readonly instances = [] as NativeInput[];
@ -37,8 +38,7 @@ export class NativeInput implements AbstractInput {
this.nativeHandle = audio.record.create_recorder();
this.nativeConsumer = this.nativeHandle.create_consumer();
this.nativeConsumer.toggle_rnnoise(true);
(window as any).consumer = this.nativeConsumer; /* FIXME! */
this.nativeConsumer.toggle_rnnoise(settings.static_global(Settings.KEY_RNNOISE_FILTER));
this.nativeConsumer.callback_ended = () => {
this.filtered = true;
@ -245,7 +245,7 @@ export class NativeLevelMeter implements LevelMeter {
this.nativeRecorder = audio.record.create_recorder();
this.nativeConsumer = this.nativeRecorder.create_consumer();
this.nativeConsumer.toggle_rnnoise(true); /* FIXME! */
this.nativeConsumer.toggle_rnnoise(settings.static_global(Settings.KEY_RNNOISE_FILTER));
this.nativeFilter = this.nativeConsumer.create_filter_threshold(.5);
this.nativeFilter.set_attack_smooth(.75);

View File

@ -0,0 +1,30 @@
import {NativeClientBackend} from "tc-shared/backend/NativeClient";
import {ipcRenderer} from "electron";
import {Arguments, processArguments} from "../../shared/process-arguments";
const call_basic_action = (name: string, ...args: any[]) => ipcRenderer.send('basic-action', name, ...args);
export class NativeClientBackendImpl implements NativeClientBackend {
openChangeLog(): void {
call_basic_action("open-changelog");
}
openClientUpdater(): void {
call_basic_action("check-native-update");
}
openDeveloperTools(): void {
call_basic_action("open-dev-tools");
}
quit(): void {
call_basic_action("quit");
}
reloadWindow(): void {
call_basic_action("reload-window")
}
showDeveloperOptions(): boolean {
return processArguments.has_flag(Arguments.DEV_TOOLS);
}
}

View File

@ -0,0 +1,4 @@
import {setBackend} from "tc-shared/backend";
import {NativeClientBackendImpl} from "../backend-impl/Backend";
setBackend(new NativeClientBackendImpl());

View File

@ -0,0 +1,4 @@
import {setMenuBarDriver} from "tc-shared/ui/frames/menu-bar";
import {NativeMenuBarDriver} from "../MenuBar";
setMenuBarDriver(new NativeMenuBarDriver());

View File

@ -52,7 +52,7 @@ loader.register_task(loader.Stage.JAVASCRIPT, {
priority: 80
});
loader.register_task(loader.Stage.INITIALIZING, {
loader.register_task(loader.Stage.SETUP, {
name: "teaclient initialize persistent storage",
function: async () => {
const storage = require("./PersistentLocalStorage");
@ -154,7 +154,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
/* all files which replaces a native driver */
try {
await import("./version");
await import("./MenuBarHandler");
await import("./MenuBar");
await import("./ContextMenu");
await import("./SingleInstanceHandler");
await import("./IconHelper");
@ -164,6 +164,8 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
await import("./hooks/ExternalModal");
await import("./hooks/ServerConnection");
await import("./hooks/ChangeLogClient");
await import("./hooks/Backend");
await import("./hooks/MenuBar");
await import("./UnloadHandler");
await import("./WindowsTrayHandler");

View File

@ -0,0 +1,10 @@
import {NativeImage} from "electron";
export interface NativeMenuBarEntry {
uniqueId: string,
type: "separator" | "normal",
label?: string,
icon?: string,
disabled?: boolean,
children?: NativeMenuBarEntry[]
}

View File

@ -1,6 +1,6 @@
{
"name": "TeaClient",
"version": "1.4.12",
"version": "1.4.13",
"description": "",
"main": "main.js",
"scripts": {