Changes of win ptb v0.0.56 1
53
app/Constants.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// before we can set up (and export) our constants, we first need to grab bootstrap's constants
|
||||||
|
// so we can merge them in with our constants
|
||||||
|
function init(bootstrapConstants) {
|
||||||
|
const APP_NAME = bootstrapConstants.APP_NAME;
|
||||||
|
const API_ENDPOINT = bootstrapConstants.API_ENDPOINT;
|
||||||
|
const NEW_UPDATE_ENDPOINT = bootstrapConstants.NEW_UPDATE_ENDPOINT;
|
||||||
|
const UPDATE_ENDPOINT = bootstrapConstants.UPDATE_ENDPOINT;
|
||||||
|
const APP_ID = bootstrapConstants.APP_ID;
|
||||||
|
const DEFAULT_MAIN_WINDOW_ID = 0;
|
||||||
|
const MAIN_APP_DIRNAME = __dirname;
|
||||||
|
const UpdaterEvents = {
|
||||||
|
UPDATE_NOT_AVAILABLE: 'UPDATE_NOT_AVAILABLE',
|
||||||
|
CHECKING_FOR_UPDATES: 'CHECKING_FOR_UPDATES',
|
||||||
|
UPDATE_ERROR: 'UPDATE_ERROR',
|
||||||
|
UPDATE_MANUALLY: 'UPDATE_MANUALLY',
|
||||||
|
UPDATE_AVAILABLE: 'UPDATE_AVAILABLE',
|
||||||
|
MODULE_INSTALL_PROGRESS: 'MODULE_INSTALL_PROGRESS',
|
||||||
|
UPDATE_DOWNLOADED: 'UPDATE_DOWNLOADED',
|
||||||
|
MODULE_INSTALLED: 'MODULE_INSTALLED',
|
||||||
|
CHECK_FOR_UPDATES: 'CHECK_FOR_UPDATES',
|
||||||
|
QUIT_AND_INSTALL: 'QUIT_AND_INSTALL',
|
||||||
|
MODULE_INSTALL: 'MODULE_INSTALL',
|
||||||
|
MODULE_QUERY: 'MODULE_QUERY',
|
||||||
|
UPDATER_HISTORY_QUERY_AND_TRUNCATE: 'UPDATER_HISTORY_QUERY_AND_TRUNCATE',
|
||||||
|
UPDATER_HISTORY_RESPONSE: 'UPDATER_HISTORY_RESPONSE'
|
||||||
|
};
|
||||||
|
const MenuEvents = {
|
||||||
|
OPEN_HELP: 'menu:open-help',
|
||||||
|
OPEN_SETTINGS: 'menu:open-settings',
|
||||||
|
CHECK_FOR_UPDATES: 'menu:check-for-updates'
|
||||||
|
};
|
||||||
|
const exported = {
|
||||||
|
APP_NAME,
|
||||||
|
DEFAULT_MAIN_WINDOW_ID,
|
||||||
|
MAIN_APP_DIRNAME,
|
||||||
|
APP_ID,
|
||||||
|
API_ENDPOINT,
|
||||||
|
NEW_UPDATE_ENDPOINT,
|
||||||
|
UPDATE_ENDPOINT,
|
||||||
|
UpdaterEvents,
|
||||||
|
MenuEvents
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key of Object.keys(exported)) {
|
||||||
|
module.exports[key] = exported[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init
|
||||||
|
};
|
27
app/GPUSettings.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.getEnableHardwareAcceleration = getEnableHardwareAcceleration;
|
||||||
|
exports.setEnableHardwareAcceleration = setEnableHardwareAcceleration;
|
||||||
|
|
||||||
|
var _electron = require("electron");
|
||||||
|
|
||||||
|
var _appSettings = require("./appSettings");
|
||||||
|
|
||||||
|
const settings = (0, _appSettings.getSettings)();
|
||||||
|
|
||||||
|
function getEnableHardwareAcceleration() {
|
||||||
|
// TODO: This should probably a constant
|
||||||
|
return settings.get('enableHardwareAcceleration', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEnableHardwareAcceleration(enableHardwareAcceleration) {
|
||||||
|
settings.set('enableHardwareAcceleration', enableHardwareAcceleration);
|
||||||
|
settings.save();
|
||||||
|
|
||||||
|
_electron.app.relaunch();
|
||||||
|
|
||||||
|
_electron.app.exit(0);
|
||||||
|
}
|
100
app/appBadge.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.init = init;
|
||||||
|
exports.hasInit = void 0;
|
||||||
|
|
||||||
|
var _electron = require("electron");
|
||||||
|
|
||||||
|
var _utils = require("./utils");
|
||||||
|
|
||||||
|
var _mainScreen = require("./mainScreen");
|
||||||
|
|
||||||
|
var _ipcMain = _interopRequireDefault(require("./ipcMain"));
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
let hasInit = false;
|
||||||
|
exports.hasInit = hasInit;
|
||||||
|
let lastIndex;
|
||||||
|
let appIcons;
|
||||||
|
/**
|
||||||
|
* Used on Windows to set the taskbar icon
|
||||||
|
*/
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
// Only init on win32 platforms
|
||||||
|
if (process.platform !== 'win32') return;
|
||||||
|
|
||||||
|
if (hasInit) {
|
||||||
|
console.warn('appBadge: Has already init! Cancelling init.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.hasInit = hasInit = true;
|
||||||
|
lastIndex = null;
|
||||||
|
appIcons = [];
|
||||||
|
const resourcePath = `app/images/badges`;
|
||||||
|
|
||||||
|
for (let i = 1; i <= 11; i++) {
|
||||||
|
appIcons.push((0, _utils.exposeModuleResource)(resourcePath, `badge-${i}.ico`));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ipcMain.default.on('APP_BADGE_SET', (_event, count) => setAppBadge(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAppBadge(count) {
|
||||||
|
const win = _electron.BrowserWindow.fromId((0, _mainScreen.getMainWindowId)());
|
||||||
|
|
||||||
|
const {
|
||||||
|
index,
|
||||||
|
description
|
||||||
|
} = getOverlayIconData(count); // Prevent setting a new icon when the icon is the same
|
||||||
|
|
||||||
|
if (lastIndex !== index) {
|
||||||
|
if (index == null) {
|
||||||
|
win.setOverlayIcon(null, description);
|
||||||
|
} else {
|
||||||
|
win.setOverlayIcon(appIcons[index], description);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* -1 is bullet
|
||||||
|
* 0 is nothing
|
||||||
|
* 1-9 is a number badge
|
||||||
|
* 10+ is `9+`
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function getOverlayIconData(count) {
|
||||||
|
// Unread message badge
|
||||||
|
if (count === -1) {
|
||||||
|
return {
|
||||||
|
index: 10,
|
||||||
|
// this.appIcons.length - 1
|
||||||
|
description: `Unread messages`
|
||||||
|
};
|
||||||
|
} // Clear overlay icon
|
||||||
|
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
return {
|
||||||
|
index: null,
|
||||||
|
// null is used to clear the overlay icon
|
||||||
|
description: 'No Notifications'
|
||||||
|
};
|
||||||
|
} // Notification badge
|
||||||
|
|
||||||
|
|
||||||
|
const index = Math.max(1, Math.min(count, 10)) - 1; // arrays are 0 based
|
||||||
|
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
description: `${index} notifications`
|
||||||
|
};
|
||||||
|
}
|
71
app/appConfig.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.init = init;
|
||||||
|
exports.hasInit = void 0;
|
||||||
|
|
||||||
|
var autoStart = _interopRequireWildcard(require("./autoStart"));
|
||||||
|
|
||||||
|
var _appSettings = require("./appSettings");
|
||||||
|
|
||||||
|
var _ipcMain = _interopRequireDefault(require("./ipcMain"));
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||||
|
|
||||||
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
|
||||||
|
const settings = (0, _appSettings.getSettings)();
|
||||||
|
|
||||||
|
const NOOP = () => {};
|
||||||
|
|
||||||
|
let hasInit = false;
|
||||||
|
exports.hasInit = hasInit;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (hasInit) {
|
||||||
|
console.warn('appConfig: Has already init! Cancelling init.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.hasInit = hasInit = true;
|
||||||
|
|
||||||
|
_ipcMain.default.on('TOGGLE_MINIMIZE_TO_TRAY', (_event, value) => setMinimizeOnClose(value));
|
||||||
|
|
||||||
|
_ipcMain.default.on('TOGGLE_OPEN_ON_STARTUP', (_event, value) => toggleRunOnStartup(value));
|
||||||
|
|
||||||
|
_ipcMain.default.on('TOGGLE_START_MINIMIZED', (_event, value) => toggleStartMinimized(value));
|
||||||
|
|
||||||
|
_ipcMain.default.on('UPDATE_OPEN_ON_STARTUP', _event => updateOpenOnStartup());
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMinimizeOnClose(minimizeToTray) {
|
||||||
|
settings.set('MINIMIZE_TO_TRAY', minimizeToTray);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRunOnStartup(openOnStartup) {
|
||||||
|
settings.set('OPEN_ON_STARTUP', openOnStartup);
|
||||||
|
|
||||||
|
if (openOnStartup) {
|
||||||
|
autoStart.install(NOOP);
|
||||||
|
} else {
|
||||||
|
autoStart.uninstall(NOOP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleStartMinimized(startMinimized) {
|
||||||
|
settings.set('START_MINIMIZED', startMinimized);
|
||||||
|
autoStart.isInstalled(installed => {
|
||||||
|
// Only update the registry for this toggle if the app was already set to autorun
|
||||||
|
if (installed) {
|
||||||
|
autoStart.install(NOOP);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOpenOnStartup() {
|
||||||
|
autoStart.update(NOOP);
|
||||||
|
}
|
21
app/appFeatures.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.init = init;
|
||||||
|
exports.getFeatures = getFeatures;
|
||||||
|
|
||||||
|
var _FeatureFlags = _interopRequireDefault(require("../common/FeatureFlags"));
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
let features;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
features = new _FeatureFlags.default();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFeatures() {
|
||||||
|
return features;
|
||||||
|
}
|
3
app/appSettings.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = require('./bootstrapModules').appSettings;
|
164
app/applicationMenu/darwin.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = void 0;
|
||||||
|
|
||||||
|
var _electron = require("electron");
|
||||||
|
|
||||||
|
var _securityUtils = require("../../common/securityUtils");
|
||||||
|
|
||||||
|
var Constants = _interopRequireWildcard(require("../Constants"));
|
||||||
|
|
||||||
|
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||||
|
|
||||||
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
|
||||||
|
const {
|
||||||
|
MenuEvents
|
||||||
|
} = Constants;
|
||||||
|
const SEPARATOR = {
|
||||||
|
type: 'separator'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getWindow() {
|
||||||
|
let window = _electron.BrowserWindow.getFocusedWindow();
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
const windowList = _electron.BrowserWindow.getAllWindows();
|
||||||
|
|
||||||
|
if (windowList && windowList[0]) {
|
||||||
|
window = windowList[0];
|
||||||
|
window.show();
|
||||||
|
window.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _default = [{
|
||||||
|
label: 'Discord',
|
||||||
|
submenu: [{
|
||||||
|
label: 'About Discord',
|
||||||
|
selector: 'orderFrontStandardAboutPanel:'
|
||||||
|
}, {
|
||||||
|
label: 'Check for Updates...',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.CHECK_FOR_UPDATES)
|
||||||
|
}, {
|
||||||
|
label: 'Acknowledgements',
|
||||||
|
click: () => (0, _securityUtils.saferShellOpenExternal)('https://discord.com/acknowledgements')
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'Preferences',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.OPEN_SETTINGS),
|
||||||
|
accelerator: 'Command+,'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'Services',
|
||||||
|
submenu: []
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'Hide Discord',
|
||||||
|
selector: 'hide:',
|
||||||
|
accelerator: 'Command+H'
|
||||||
|
}, {
|
||||||
|
label: 'Hide Others',
|
||||||
|
selector: 'hideOtherApplications:',
|
||||||
|
accelerator: 'Command+Alt+H'
|
||||||
|
}, {
|
||||||
|
label: 'Show All',
|
||||||
|
selector: 'unhideAllApplications:'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'Quit',
|
||||||
|
click: () => _electron.app.quit(),
|
||||||
|
accelerator: 'Command+Q'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [{
|
||||||
|
role: 'undo',
|
||||||
|
accelerator: 'Command+Z'
|
||||||
|
}, {
|
||||||
|
role: 'redo',
|
||||||
|
accelerator: 'Shift+Command+Z'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
role: 'cut',
|
||||||
|
accelerator: 'Command+X'
|
||||||
|
}, {
|
||||||
|
role: 'copy',
|
||||||
|
accelerator: 'Command+C'
|
||||||
|
}, {
|
||||||
|
role: 'paste',
|
||||||
|
accelerator: 'Command+V'
|
||||||
|
}, {
|
||||||
|
role: 'selectAll',
|
||||||
|
accelerator: 'Command+A'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: 'View',
|
||||||
|
submenu: [{
|
||||||
|
label: 'Reload',
|
||||||
|
click: () => {
|
||||||
|
const window = getWindow();
|
||||||
|
|
||||||
|
if (window) {
|
||||||
|
window.webContents.reloadIgnoringCache();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accelerator: 'Command+R'
|
||||||
|
}, {
|
||||||
|
label: 'Toggle Full Screen',
|
||||||
|
click: () => {
|
||||||
|
const window = getWindow();
|
||||||
|
|
||||||
|
if (window) {
|
||||||
|
window.setFullScreen(!window.isFullScreen());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accelerator: 'Command+Control+F'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'Developer',
|
||||||
|
submenu: [{
|
||||||
|
label: 'Toggle Developer Tools',
|
||||||
|
click: () => {
|
||||||
|
const window = getWindow();
|
||||||
|
|
||||||
|
if (window) {
|
||||||
|
window.toggleDevTools();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accelerator: 'Alt+Command+I'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: 'Window',
|
||||||
|
submenu: [{
|
||||||
|
label: 'Minimize',
|
||||||
|
selector: 'performMiniaturize:',
|
||||||
|
accelerator: 'Command+M'
|
||||||
|
}, {
|
||||||
|
label: 'Zoom',
|
||||||
|
selector: 'performZoom:'
|
||||||
|
}, {
|
||||||
|
label: 'Close',
|
||||||
|
accelerator: 'Command+W',
|
||||||
|
click: (_, window) => {
|
||||||
|
// Main window
|
||||||
|
if (window == null || window.windowKey == null) {
|
||||||
|
_electron.Menu.sendActionToFirstResponder('hide:');
|
||||||
|
} else {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'Bring All to Front',
|
||||||
|
selector: 'arrangeInFront:'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: 'Help',
|
||||||
|
submenu: [{
|
||||||
|
label: 'Discord Help',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.OPEN_HELP)
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
exports.default = _default;
|
||||||
|
module.exports = exports.default;
|
15
app/applicationMenu/index.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = void 0;
|
||||||
|
|
||||||
|
var _electron = require("electron");
|
||||||
|
|
||||||
|
const menu = require('./' + process.platform);
|
||||||
|
|
||||||
|
var _default = _electron.Menu.buildFromTemplate(menu);
|
||||||
|
|
||||||
|
exports.default = _default;
|
||||||
|
module.exports = exports.default;
|
83
app/applicationMenu/linux.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = void 0;
|
||||||
|
|
||||||
|
var _electron = require("electron");
|
||||||
|
|
||||||
|
var Constants = _interopRequireWildcard(require("../Constants"));
|
||||||
|
|
||||||
|
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||||
|
|
||||||
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
|
||||||
|
const {
|
||||||
|
MenuEvents
|
||||||
|
} = Constants;
|
||||||
|
const SEPARATOR = {
|
||||||
|
type: 'separator'
|
||||||
|
};
|
||||||
|
var _default = [{
|
||||||
|
label: '&File',
|
||||||
|
submenu: [{
|
||||||
|
label: '&Options',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.OPEN_SETTINGS),
|
||||||
|
accelerator: 'Control+,'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'E&xit',
|
||||||
|
click: () => _electron.app.quit(),
|
||||||
|
accelerator: 'Control+Q'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: '&Edit',
|
||||||
|
submenu: [{
|
||||||
|
role: 'undo',
|
||||||
|
accelerator: 'Control+Z'
|
||||||
|
}, {
|
||||||
|
role: 'redo',
|
||||||
|
accelerator: 'Shift+Control+Z'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
role: 'cut',
|
||||||
|
accelerator: 'Control+X'
|
||||||
|
}, {
|
||||||
|
role: 'copy',
|
||||||
|
accelerator: 'Control+C'
|
||||||
|
}, {
|
||||||
|
role: 'paste',
|
||||||
|
accelerator: 'Control+V'
|
||||||
|
}, {
|
||||||
|
role: 'selectAll',
|
||||||
|
accelerator: 'Control+A'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: '&View',
|
||||||
|
submenu: [{
|
||||||
|
label: '&Reload',
|
||||||
|
click: () => _electron.BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache(),
|
||||||
|
accelerator: 'Control+R'
|
||||||
|
}, {
|
||||||
|
label: 'Toggle &Full Screen',
|
||||||
|
click: () => _electron.BrowserWindow.getFocusedWindow().setFullScreen(!_electron.BrowserWindow.getFocusedWindow().isFullScreen()),
|
||||||
|
accelerator: 'Control+Shift+F'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: '&Developer',
|
||||||
|
submenu: [{
|
||||||
|
label: 'Toggle Developer &Tools',
|
||||||
|
click: () => _electron.BrowserWindow.getFocusedWindow().toggleDevTools(),
|
||||||
|
accelerator: 'Control+Shift+I'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: '&Help',
|
||||||
|
submenu: [{
|
||||||
|
label: 'Check for Updates',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.CHECK_FOR_UPDATES)
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'Discord Help',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.OPEN_HELP)
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
exports.default = _default;
|
||||||
|
module.exports = exports.default;
|
62
app/applicationMenu/win32.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = void 0;
|
||||||
|
|
||||||
|
var _electron = require("electron");
|
||||||
|
|
||||||
|
var Constants = _interopRequireWildcard(require("../Constants"));
|
||||||
|
|
||||||
|
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||||
|
|
||||||
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
|
||||||
|
const {
|
||||||
|
MenuEvents
|
||||||
|
} = Constants;
|
||||||
|
const SEPARATOR = {
|
||||||
|
type: 'separator'
|
||||||
|
};
|
||||||
|
var _default = [{
|
||||||
|
label: '&File',
|
||||||
|
submenu: [{
|
||||||
|
label: '&Options',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.OPEN_SETTINGS),
|
||||||
|
accelerator: 'Ctrl+,'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: '&Exit',
|
||||||
|
click: () => _electron.app.quit(),
|
||||||
|
accelerator: 'Alt+F4'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: '&View',
|
||||||
|
submenu: [{
|
||||||
|
label: '&Reload',
|
||||||
|
click: () => _electron.BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache(),
|
||||||
|
accelerator: 'Control+R'
|
||||||
|
}, {
|
||||||
|
label: 'Toggle &Full Screen',
|
||||||
|
click: () => _electron.BrowserWindow.getFocusedWindow().setFullScreen(!_electron.BrowserWindow.getFocusedWindow().isFullScreen()),
|
||||||
|
accelerator: 'Control+Shift+F'
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: '&Developer',
|
||||||
|
submenu: [{
|
||||||
|
label: 'Toggle Developer &Tools',
|
||||||
|
click: () => _electron.BrowserWindow.getFocusedWindow().toggleDevTools(),
|
||||||
|
accelerator: 'Control+Shift+I'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: '&Help',
|
||||||
|
submenu: [{
|
||||||
|
label: 'Check for Updates',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.CHECK_FOR_UPDATES)
|
||||||
|
}, SEPARATOR, {
|
||||||
|
label: 'Discord Help',
|
||||||
|
click: () => _electron.app.emit(MenuEvents.OPEN_HELP)
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
exports.default = _default;
|
||||||
|
module.exports = exports.default;
|
3
app/autoStart.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = require('./bootstrapModules').autoStart;
|
15
app/bootstrapModules.js
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
let hasInit = false;
|
||||||
|
|
||||||
|
exports.init = function (bootstrapModules) {
|
||||||
|
if (hasInit) {
|
||||||
|
throw new Error(`bootstrapModules has already init`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const mod of Object.keys(bootstrapModules)) {
|
||||||
|
exports[mod] = bootstrapModules[mod];
|
||||||
|
}
|
||||||
|
|
||||||
|
hasInit = true;
|
||||||
|
};
|
3
app/buildInfo.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = require('./bootstrapModules').buildInfo;
|
5
app/crashReporterSetup.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const bootstrapCrashReporterSetup = require('./bootstrapModules').crashReporterSetup;
|
||||||
|
|
||||||
|
module.exports = bootstrapCrashReporterSetup != null ? bootstrapCrashReporterSetup : require('../common/crashReporterSetup');
|
3865
app/data/cacert.pem
Normal file
11
app/discord_native/browser/accessibility.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
ACCESSIBILITY_GET_ENABLED
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
electron.ipcMain.handle(ACCESSIBILITY_GET_ENABLED, async _ => {
|
||||||
|
return electron.app.accessibilitySupportEnabled;
|
||||||
|
});
|
100
app/discord_native/browser/app.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.injectBuildInfo = injectBuildInfo;
|
||||||
|
exports.injectModuleUpdater = injectModuleUpdater;
|
||||||
|
exports.injectUpdater = injectUpdater;
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
APP_GET_RELEASE_CHANNEL_SYNC,
|
||||||
|
APP_GET_HOST_VERSION_SYNC,
|
||||||
|
APP_GET_MODULE_VERSIONS,
|
||||||
|
APP_GET_PATH,
|
||||||
|
APP_SET_BADGE_COUNT,
|
||||||
|
APP_DOCK_SET_BADGE,
|
||||||
|
APP_DOCK_BOUNCE,
|
||||||
|
APP_DOCK_CANCEL_BOUNCE,
|
||||||
|
APP_RELAUNCH,
|
||||||
|
APP_GET_DEFAULT_DOUBLE_CLICK_ACTION
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let injectedBuildInfo = null;
|
||||||
|
let injectedModuleUpdater = null;
|
||||||
|
let injectedUpdater = null;
|
||||||
|
|
||||||
|
function injectBuildInfo(buildInfo) {
|
||||||
|
injectedBuildInfo = buildInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectModuleUpdater(moduleUpdater) {
|
||||||
|
injectedModuleUpdater = moduleUpdater;
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectUpdater(updater) {
|
||||||
|
injectedUpdater = updater;
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.ipcMain.on(APP_GET_RELEASE_CHANNEL_SYNC, event => {
|
||||||
|
event.returnValue = injectedBuildInfo.releaseChannel;
|
||||||
|
});
|
||||||
|
electron.ipcMain.on(APP_GET_HOST_VERSION_SYNC, event => {
|
||||||
|
event.returnValue = electron.app.getVersion();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function newUpdaterGetModuleVersions(updater) {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
return (await updater.queryCurrentVersions()).current_modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.ipcMain.handle(APP_GET_MODULE_VERSIONS, async _ => {
|
||||||
|
var _injectedUpdater;
|
||||||
|
|
||||||
|
const newUpdater = (_injectedUpdater = injectedUpdater) === null || _injectedUpdater === void 0 ? void 0 : _injectedUpdater.getUpdater();
|
||||||
|
|
||||||
|
if (newUpdater != null) {
|
||||||
|
return newUpdaterGetModuleVersions(newUpdater);
|
||||||
|
}
|
||||||
|
|
||||||
|
const versions = {};
|
||||||
|
const installed = injectedModuleUpdater != null ? injectedModuleUpdater.getInstalled() : {};
|
||||||
|
|
||||||
|
for (const name of Object.keys(installed)) {
|
||||||
|
versions[name] = installed[name].installedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return versions;
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(APP_GET_PATH, async (_, path) => {
|
||||||
|
return electron.app.getPath(path);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(APP_SET_BADGE_COUNT, async (_, count) => {
|
||||||
|
electron.app.setBadgeCount(count);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(APP_DOCK_SET_BADGE, async (_, badge) => {
|
||||||
|
if (electron.app.dock != null) {
|
||||||
|
electron.app.dock.setBadge(badge);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(APP_DOCK_BOUNCE, async (_, type) => {
|
||||||
|
if (electron.app.dock != null) {
|
||||||
|
return electron.app.dock.bounce(type);
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(APP_DOCK_CANCEL_BOUNCE, async (_, id) => {
|
||||||
|
if (electron.app.dock != null) {
|
||||||
|
electron.app.dock.cancelBounce(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(APP_RELAUNCH, async _ => {
|
||||||
|
electron.app.relaunch();
|
||||||
|
electron.app.exit(0);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(APP_GET_DEFAULT_DOUBLE_CLICK_ACTION, async _ => {
|
||||||
|
return electron.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
|
||||||
|
});
|
19
app/discord_native/browser/clipboard.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
CLIPBOARD_COPY,
|
||||||
|
CLIPBOARD_CUT,
|
||||||
|
CLIPBOARD_PASTE
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
electron.ipcMain.handle(CLIPBOARD_COPY, async _ => {
|
||||||
|
electron.webContents.getFocusedWebContents().copy();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(CLIPBOARD_CUT, async _ => {
|
||||||
|
electron.webContents.getFocusedWebContents().cut();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(CLIPBOARD_PASTE, async _ => {
|
||||||
|
electron.webContents.getFocusedWebContents().paste();
|
||||||
|
});
|
28
app/discord_native/browser/constants.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
CONSTANTS_GET
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
const {
|
||||||
|
APP_NAME,
|
||||||
|
APP_ID,
|
||||||
|
API_ENDPOINT,
|
||||||
|
UPDATE_ENDPOINT
|
||||||
|
} = require('../../Constants');
|
||||||
|
|
||||||
|
const exposedConstants = {
|
||||||
|
APP_NAME,
|
||||||
|
APP_ID,
|
||||||
|
API_ENDPOINT,
|
||||||
|
UPDATE_ENDPOINT
|
||||||
|
};
|
||||||
|
electron.ipcMain.handle(CONSTANTS_GET, async (_, name) => {
|
||||||
|
if (!exposedConstants.hasOwnProperty(name)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exposedConstants[name];
|
||||||
|
});
|
40
app/discord_native/browser/crashReporter.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const lodash = require('lodash');
|
||||||
|
|
||||||
|
const {
|
||||||
|
reconcileCrashReporterMetadata
|
||||||
|
} = require('../../../common/crashReporterUtils');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getElectronMajorVersion
|
||||||
|
} = require('../../../common/processUtils');
|
||||||
|
|
||||||
|
const {
|
||||||
|
metadata
|
||||||
|
} = require('../../crashReporterSetup');
|
||||||
|
|
||||||
|
const {
|
||||||
|
CRASH_REPORTER_UPDATE_METADATA
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
electron.ipcMain.handle(CRASH_REPORTER_UPDATE_METADATA, async (_, additional_metadata) => {
|
||||||
|
const final_metadata = lodash.defaultsDeep({}, metadata, additional_metadata || {});
|
||||||
|
const result = {
|
||||||
|
metadata: final_metadata
|
||||||
|
}; // In Electron 9 we only start the crashReporter once and let reconcileCrashReporterMetadata
|
||||||
|
// do the work of keeping `extra` up-to-date. Prior to this we would simply start crashReporter
|
||||||
|
// again to apply new metadata as well as pass the full arguments back to the renderer so it
|
||||||
|
// could do similarly.
|
||||||
|
|
||||||
|
if (getElectronMajorVersion() < 9) {
|
||||||
|
const args = getCrashReporterArgs(final_metadata);
|
||||||
|
electron.crashReporter.start(args);
|
||||||
|
result.args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
reconcileCrashReporterMetadata(electron.crashReporter, final_metadata);
|
||||||
|
return result;
|
||||||
|
});
|
34
app/discord_native/browser/features.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.injectFeaturesBackend = injectFeaturesBackend;
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FEATURES_GET_BROWSER_FEATURES
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let injectedFeatures = null;
|
||||||
|
|
||||||
|
function getFeatures() {
|
||||||
|
return injectedFeatures != null ? injectedFeatures : {
|
||||||
|
getSupported: () => {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
supports: () => {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
declareSupported: () => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectFeaturesBackend(features) {
|
||||||
|
injectedFeatures = features;
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.ipcMain.on(FEATURES_GET_BROWSER_FEATURES, event => {
|
||||||
|
event.returnValue = getFeatures().getSupported();
|
||||||
|
});
|
27
app/discord_native/browser/fileManager.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FILE_MANAGER_GET_MODULE_PATH,
|
||||||
|
FILE_MANAGER_GET_MODULE_DATA_PATH_SYNC,
|
||||||
|
FILE_MANAGER_SHOW_SAVE_DIALOG,
|
||||||
|
FILE_MANAGER_SHOW_OPEN_DIALOG,
|
||||||
|
FILE_MANAGER_SHOW_ITEM_IN_FOLDER
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
electron.ipcMain.handle(FILE_MANAGER_GET_MODULE_PATH, async _ => {
|
||||||
|
return global.moduleDataPath || global.modulePath;
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(FILE_MANAGER_SHOW_SAVE_DIALOG, async (_, dialogOptions) => {
|
||||||
|
return await electron.dialog.showSaveDialog(dialogOptions);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(FILE_MANAGER_SHOW_OPEN_DIALOG, async (_, dialogOptions) => {
|
||||||
|
return await electron.dialog.showOpenDialog(dialogOptions);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(FILE_MANAGER_SHOW_ITEM_IN_FOLDER, async (_, path) => {
|
||||||
|
electron.shell.showItemInFolder(path);
|
||||||
|
});
|
||||||
|
electron.ipcMain.on(FILE_MANAGER_GET_MODULE_DATA_PATH_SYNC, event => {
|
||||||
|
event.returnValue = global.moduleDataPath || global.modulePath;
|
||||||
|
});
|
28
app/discord_native/browser/gpuSettings.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.injectGpuSettingsBackend = injectGpuSettingsBackend;
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
GPU_SETTINGS_SET_ENABLE_HWACCEL,
|
||||||
|
GPU_SETTINGS_GET_ENABLE_HWACCEL_SYNC
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let injectedGpuSettings = null;
|
||||||
|
|
||||||
|
function injectGpuSettingsBackend(gpuSettings) {
|
||||||
|
injectedGpuSettings = gpuSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.ipcMain.handle(GPU_SETTINGS_SET_ENABLE_HWACCEL, async (_, enable) => {
|
||||||
|
if (injectedGpuSettings) {
|
||||||
|
injectedGpuSettings.setEnableHardwareAcceleration(enable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcMain.on(GPU_SETTINGS_GET_ENABLE_HWACCEL_SYNC, event => {
|
||||||
|
event.returnValue = injectedGpuSettings != null ? injectedGpuSettings.getEnableHardwareAcceleration() : false;
|
||||||
|
});
|
115
app/discord_native/browser/nativeModules.js
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.injectModuleUpdater = injectModuleUpdater;
|
||||||
|
exports.injectUpdater = injectUpdater;
|
||||||
|
|
||||||
|
const childProcess = require('child_process');
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
once
|
||||||
|
} = require('events');
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
const {
|
||||||
|
NATIVE_MODULES_GET_PATHS,
|
||||||
|
NATIVE_MODULES_INSTALL,
|
||||||
|
NATIVE_MODULES_FINISH_UPDATER_BOOTSTRAP,
|
||||||
|
NATIVE_MODULES_GET_HAS_NEW_UPDATER
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let injectedModuleUpdater = null;
|
||||||
|
let injectedUpdater = null;
|
||||||
|
|
||||||
|
function injectModuleUpdater(moduleUpdater) {
|
||||||
|
injectedModuleUpdater = moduleUpdater;
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectUpdater(updater) {
|
||||||
|
injectedUpdater = updater;
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.ipcMain.on(NATIVE_MODULES_GET_PATHS, event => {
|
||||||
|
event.returnValue = {
|
||||||
|
mainAppDirname: global.mainAppDirname,
|
||||||
|
browserModulePaths: require('module').globalPaths
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async function newUpdaterInstall(updater, moduleName) {
|
||||||
|
try {
|
||||||
|
await updater.installModule(moduleName);
|
||||||
|
await updater.commitModules();
|
||||||
|
} catch (e) {
|
||||||
|
// Report the same error to the app that we used to.
|
||||||
|
throw new Error(`Failed to install ${moduleName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.ipcMain.handle(NATIVE_MODULES_INSTALL, async (_, moduleName) => {
|
||||||
|
var _injectedUpdater;
|
||||||
|
|
||||||
|
const newUpdater = (_injectedUpdater = injectedUpdater) === null || _injectedUpdater === void 0 ? void 0 : _injectedUpdater.getUpdater();
|
||||||
|
|
||||||
|
if (newUpdater != null) {
|
||||||
|
return newUpdaterInstall(newUpdater, moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updater = injectedModuleUpdater;
|
||||||
|
|
||||||
|
if (!updater) {
|
||||||
|
throw new Error('Module updater is not available!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitForInstall = new Promise((resolve, reject) => {
|
||||||
|
const installedHandler = installedModuleEvent => {
|
||||||
|
if (installedModuleEvent.name === moduleName) {
|
||||||
|
updater.events.removeListener(updater.INSTALLED_MODULE, installedHandler);
|
||||||
|
|
||||||
|
if (installedModuleEvent.succeeded) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Failed to install ${moduleName}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updater.events.on(updater.INSTALLED_MODULE, installedHandler);
|
||||||
|
});
|
||||||
|
updater.install(moduleName, false);
|
||||||
|
await waitForInstall;
|
||||||
|
});
|
||||||
|
electron.ipcMain.on(NATIVE_MODULES_GET_HAS_NEW_UPDATER, event => {
|
||||||
|
var _injectedUpdater2;
|
||||||
|
|
||||||
|
event.returnValue = ((_injectedUpdater2 = injectedUpdater) === null || _injectedUpdater2 === void 0 ? void 0 : _injectedUpdater2.getUpdater()) != null;
|
||||||
|
}); // This endpoint is a bit special in the sense that it's exposed from
|
||||||
|
// discord_updater_bootstrap instead of discord_desktop_core. The reason for
|
||||||
|
// this is so that a malicious app can't pass in an arbitrary version number to
|
||||||
|
// launch.
|
||||||
|
|
||||||
|
electron.ipcMain.on(NATIVE_MODULES_FINISH_UPDATER_BOOTSTRAP, async (_, [major, minor, revision]) => {
|
||||||
|
// TODO(eiz): This code is currently duplicated between the updater and here
|
||||||
|
// due to bootstrapping reasons. I'd like to not have it be that way.
|
||||||
|
if (typeof major !== 'number' || typeof minor !== 'number' || typeof revision !== 'number') {
|
||||||
|
throw new Error('You tried.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostVersionStr = `${major}.${minor}.${revision}`;
|
||||||
|
const hostExePath = path.join(path.dirname(process.execPath), '..', `app-${hostVersionStr}`, path.basename(process.execPath));
|
||||||
|
electron.app.once('will-quit', () => {
|
||||||
|
childProcess.spawn(hostExePath, [], {
|
||||||
|
detached: true,
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(`Restarting from ${path.resolve(process.execPath)} to ${path.resolve(hostExePath)}`);
|
||||||
|
electron.app.quit();
|
||||||
|
});
|
38
app/discord_native/browser/powerMonitor.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
POWER_MONITOR_RESUME,
|
||||||
|
POWER_MONITOR_SUSPEND,
|
||||||
|
POWER_MONITOR_LOCK_SCREEN,
|
||||||
|
POWER_MONITOR_UNLOCK_SCREEN,
|
||||||
|
POWER_MONITOR_GET_SYSTEM_IDLE_TIME
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
electron.ipcMain.handle(POWER_MONITOR_GET_SYSTEM_IDLE_TIME, async _ => {
|
||||||
|
return electron.powerMonitor.getSystemIdleTime() * 1000;
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendToAllWindows(channel) {
|
||||||
|
electron.BrowserWindow.getAllWindows().forEach(win => {
|
||||||
|
const contents = win.webContents;
|
||||||
|
|
||||||
|
if (contents != null) {
|
||||||
|
contents.send(channel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.powerMonitor.on('resume', () => {
|
||||||
|
sendToAllWindows(POWER_MONITOR_RESUME);
|
||||||
|
});
|
||||||
|
electron.powerMonitor.on('suspend', () => {
|
||||||
|
sendToAllWindows(POWER_MONITOR_SUSPEND);
|
||||||
|
});
|
||||||
|
electron.powerMonitor.on('lock-screen', () => {
|
||||||
|
sendToAllWindows(POWER_MONITOR_LOCK_SCREEN);
|
||||||
|
});
|
||||||
|
electron.powerMonitor.on('unlock-screen', () => {
|
||||||
|
sendToAllWindows(POWER_MONITOR_UNLOCK_SCREEN);
|
||||||
|
});
|
28
app/discord_native/browser/powerSaveBlocker.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
POWER_SAVE_BLOCKER_BLOCK_DISPLAY_SLEEP,
|
||||||
|
POWER_SAVE_BLOCKER_UNBLOCK_DISPLAY_SLEEP,
|
||||||
|
POWER_SAVE_BLOCKER_CLEANUP_DISPLAY_SLEEP
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
const powerSaveBlockerIds = new Set();
|
||||||
|
electron.ipcMain.handle(POWER_SAVE_BLOCKER_BLOCK_DISPLAY_SLEEP, async _ => {
|
||||||
|
const newId = electron.powerSaveBlocker.start('prevent-display-sleep');
|
||||||
|
powerSaveBlockerIds.add(newId);
|
||||||
|
return newId;
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(POWER_SAVE_BLOCKER_UNBLOCK_DISPLAY_SLEEP, async (_, id) => {
|
||||||
|
electron.powerSaveBlocker.stop(id);
|
||||||
|
powerSaveBlockerIds.delete(id);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(POWER_SAVE_BLOCKER_CLEANUP_DISPLAY_SLEEP, async _ => {
|
||||||
|
// cleanup all previous sleeps
|
||||||
|
for (const id of powerSaveBlockerIds) {
|
||||||
|
electron.powerSaveBlocker.stop(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
powerSaveBlockerIds.clear();
|
||||||
|
});
|
41
app/discord_native/browser/processUtils.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
const {
|
||||||
|
PROCESS_UTILS_GET_CPU_USAGE,
|
||||||
|
PROCESS_UTILS_GET_MEMORY_INFO,
|
||||||
|
PROCESS_UTILS_FLUSH_DNS_CACHE,
|
||||||
|
PROCESS_UTILS_FLUSH_COOKIES,
|
||||||
|
PROCESS_UTILS_FLUSH_STORAGE_DATA,
|
||||||
|
PROCESS_UTILS_GET_MAIN_ARGV_SYNC
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
electron.ipcMain.handle(PROCESS_UTILS_GET_CPU_USAGE, async _ => {
|
||||||
|
let totalProcessorUsagePercent = 0.0;
|
||||||
|
|
||||||
|
for (const processMetric of electron.app.getAppMetrics()) {
|
||||||
|
totalProcessorUsagePercent += processMetric.cpu.percentCPUUsage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalProcessorUsagePercent;
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(PROCESS_UTILS_GET_MEMORY_INFO, async _ => {
|
||||||
|
return process.getProcessMemoryInfo();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(PROCESS_UTILS_FLUSH_DNS_CACHE, async _ => {
|
||||||
|
const defaultSession = electron.session.defaultSession;
|
||||||
|
if (!defaultSession || !defaultSession.clearHostResolverCache) return;
|
||||||
|
defaultSession.clearHostResolverCache();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(PROCESS_UTILS_FLUSH_COOKIES, async _ => {
|
||||||
|
return electron.session.defaultSession.cookies.flushStore();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(PROCESS_UTILS_FLUSH_STORAGE_DATA, async _ => {
|
||||||
|
electron.session.defaultSession.flushStorageData();
|
||||||
|
});
|
||||||
|
electron.ipcMain.on(PROCESS_UTILS_GET_MAIN_ARGV_SYNC, event => {
|
||||||
|
event.returnValue = process.argv;
|
||||||
|
});
|
42
app/discord_native/browser/settings.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.injectSettingsBackend = injectSettingsBackend;
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
SETTINGS_GET,
|
||||||
|
SETTINGS_SET,
|
||||||
|
SETTINGS_GET_SYNC
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let injectedSettings = null;
|
||||||
|
|
||||||
|
function getSettings() {
|
||||||
|
return injectedSettings != null ? injectedSettings : {
|
||||||
|
get: () => {},
|
||||||
|
set: () => {},
|
||||||
|
save: () => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectSettingsBackend(settings) {
|
||||||
|
injectedSettings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.ipcMain.handle(SETTINGS_GET, (_, name, defaultValue) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
return settings.get(name, defaultValue);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(SETTINGS_SET, (_, name, value) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
settings.set(name, value);
|
||||||
|
settings.save();
|
||||||
|
});
|
||||||
|
electron.ipcMain.on(SETTINGS_GET_SYNC, (event, name, defaultValue) => {
|
||||||
|
const settings = getSettings();
|
||||||
|
event.returnValue = settings.get(name, defaultValue);
|
||||||
|
});
|
45
app/discord_native/browser/spellCheck.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
SPELLCHECK_REPLACE_MISSPELLING,
|
||||||
|
SPELLCHECK_GET_AVAILABLE_DICTIONARIES,
|
||||||
|
SPELLCHECK_SET_LOCALE,
|
||||||
|
SPELLCHECK_SET_LEARNED_WORDS
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let _learnedWords = new Set();
|
||||||
|
|
||||||
|
let _hasLoadedLearnedWords = false;
|
||||||
|
electron.ipcMain.handle(SPELLCHECK_REPLACE_MISSPELLING, async (event, correction) => {
|
||||||
|
event.sender.replaceMisspelling(correction);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(SPELLCHECK_GET_AVAILABLE_DICTIONARIES, async _ => {
|
||||||
|
return electron.session.defaultSession.availableSpellCheckerLanguages;
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(SPELLCHECK_SET_LOCALE, async (_, locale) => {
|
||||||
|
electron.session.defaultSession.setSpellCheckerLanguages([locale]);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(SPELLCHECK_SET_LEARNED_WORDS, async (_, newLearnedWords) => {
|
||||||
|
const session = electron.session.defaultSession;
|
||||||
|
|
||||||
|
if (!_hasLoadedLearnedWords) {
|
||||||
|
const dictionaryContents = await session.listWordsInSpellCheckerDictionary();
|
||||||
|
_learnedWords = new Set(dictionaryContents);
|
||||||
|
_hasLoadedLearnedWords = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_learnedWords.forEach(word => {
|
||||||
|
if (!newLearnedWords.has(word)) {
|
||||||
|
session.removeWordFromSpellCheckerDictionary(word);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
newLearnedWords.forEach(word => {
|
||||||
|
if (!_learnedWords.has(word)) {
|
||||||
|
session.addWordToSpellCheckerDictionary(word);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_learnedWords = new Set(newLearnedWords);
|
||||||
|
});
|
76
app/discord_native/browser/userDataCache.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var _electron = _interopRequireDefault(require("electron"));
|
||||||
|
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
|
||||||
|
var _path = _interopRequireDefault(require("path"));
|
||||||
|
|
||||||
|
var _appFeatures = require("../../appFeatures");
|
||||||
|
|
||||||
|
var paths = _interopRequireWildcard(require("../../paths"));
|
||||||
|
|
||||||
|
var _constants = require("../common/constants");
|
||||||
|
|
||||||
|
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||||
|
|
||||||
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
const {
|
||||||
|
USER_DATA_CACHE_SAVE,
|
||||||
|
USER_DATA_CACHE_GET,
|
||||||
|
USER_DATA_CACHE_DELETE
|
||||||
|
} = _constants.IPCEvents;
|
||||||
|
const features = (0, _appFeatures.getFeatures)();
|
||||||
|
|
||||||
|
function getCachePath() {
|
||||||
|
return _path.default.join(paths.getUserData(), 'userDataCache.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMigratedPath() {
|
||||||
|
return _path.default.join(paths.getUserData(), 'domainMigrated');
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheUserData(userData) {
|
||||||
|
_fs.default.writeFile(getCachePath(), userData, e => {
|
||||||
|
if (e) {
|
||||||
|
console.warn('Failed updating user data cache with error: ', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedUserData() {
|
||||||
|
try {
|
||||||
|
return JSON.parse(_fs.default.readFileSync(getCachePath()));
|
||||||
|
} catch (_err) {}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCachedUserData() {
|
||||||
|
try {
|
||||||
|
_fs.default.unlinkSync(getCachePath());
|
||||||
|
|
||||||
|
_fs.default.writeFile(getMigratedPath(), '', e => {
|
||||||
|
if (e) {
|
||||||
|
console.warn('Failed to create domainMigrated file with error: ', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (_err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
_electron.default.ipcMain.handle(USER_DATA_CACHE_GET, () => {
|
||||||
|
return getCachedUserData();
|
||||||
|
});
|
||||||
|
|
||||||
|
_electron.default.ipcMain.on(USER_DATA_CACHE_SAVE, (_event, userData) => {
|
||||||
|
cacheUserData(userData);
|
||||||
|
});
|
||||||
|
|
||||||
|
_electron.default.ipcMain.on(USER_DATA_CACHE_DELETE, _event => {
|
||||||
|
deleteCachedUserData();
|
||||||
|
});
|
||||||
|
|
||||||
|
features.declareSupported('user_data_cache');
|
104
app/discord_native/browser/window.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.injectGetWindow = injectGetWindow;
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
const {
|
||||||
|
WINDOW_BLUR,
|
||||||
|
WINDOW_CLOSE,
|
||||||
|
WINDOW_FOCUS,
|
||||||
|
WINDOW_MAXIMIZE,
|
||||||
|
WINDOW_MINIMIZE,
|
||||||
|
WINDOW_RESTORE,
|
||||||
|
WINDOW_FLASH_FRAME,
|
||||||
|
WINDOW_TOGGLE_FULLSCREEN,
|
||||||
|
WINDOW_SET_BACKGROUND_THROTTLING,
|
||||||
|
WINDOW_SET_PROGRESS_BAR,
|
||||||
|
WINDOW_IS_ALWAYS_ON_TOP,
|
||||||
|
WINDOW_SET_ALWAYS_ON_TOP
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let injectedGetWindow = _key => {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function injectGetWindow(getWindow) {
|
||||||
|
injectedGetWindow = getWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.ipcMain.handle(WINDOW_FLASH_FRAME, async (_, flag) => {
|
||||||
|
const currentWindow = injectedGetWindow();
|
||||||
|
if (currentWindow == null || currentWindow.flashFrame == null) return;
|
||||||
|
currentWindow.flashFrame(!currentWindow.isFocused() && flag);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_MINIMIZE, async (_, key) => {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
if (win == null) return;
|
||||||
|
win.minimize();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_RESTORE, async (_, key) => {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
if (win == null) return;
|
||||||
|
win.restore();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_MAXIMIZE, async (_, key) => {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
if (win == null) return;
|
||||||
|
|
||||||
|
if (win.isMaximized()) {
|
||||||
|
win.unmaximize();
|
||||||
|
} else {
|
||||||
|
win.maximize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_FOCUS, async (_, key) => {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
if (win == null) return;
|
||||||
|
win.show();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_SET_ALWAYS_ON_TOP, async (_, key, enabled) => {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
if (win == null) return;
|
||||||
|
win.setAlwaysOnTop(enabled);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_IS_ALWAYS_ON_TOP, async (_, key) => {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
if (win == null) return false;
|
||||||
|
return win.isAlwaysOnTop();
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_BLUR, async (_, key) => {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
|
||||||
|
if (win != null && !win.isDestroyed()) {
|
||||||
|
win.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_SET_PROGRESS_BAR, async (_, key, progress) => {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
if (win == null) return;
|
||||||
|
win.setProgressBar(progress);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_TOGGLE_FULLSCREEN, async (_, key) => {
|
||||||
|
const currentWindow = injectedGetWindow(key);
|
||||||
|
currentWindow.setFullScreen(!currentWindow.isFullScreen());
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_CLOSE, async (_, key) => {
|
||||||
|
if (key == null && process.platform === 'darwin') {
|
||||||
|
electron.Menu.sendActionToFirstResponder('hide:');
|
||||||
|
} else {
|
||||||
|
const win = injectedGetWindow(key);
|
||||||
|
if (win == null) return;
|
||||||
|
win.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle(WINDOW_SET_BACKGROUND_THROTTLING, async (_, enabled) => {
|
||||||
|
const win = injectedGetWindow();
|
||||||
|
if (win == null) return;
|
||||||
|
win.webContents.setBackgroundThrottling(enabled);
|
||||||
|
});
|
106
app/discord_native/common/constants.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.getDiscordIPCEvent = getDiscordIPCEvent;
|
||||||
|
exports.IPCEvents = void 0;
|
||||||
|
|
||||||
|
function sanitizeIPCEvents(events) {
|
||||||
|
for (const key of Object.keys(events)) {
|
||||||
|
events[key] = getDiscordIPCEvent(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDiscordIPCEvent(ev) {
|
||||||
|
return `DISCORD_${ev}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IPCEvents = sanitizeIPCEvents({
|
||||||
|
ACCESSIBILITY_GET_ENABLED: null,
|
||||||
|
APP_BADGE_SET: null,
|
||||||
|
APP_GET_RELEASE_CHANNEL_SYNC: null,
|
||||||
|
APP_GET_HOST_VERSION_SYNC: null,
|
||||||
|
APP_GET_MODULE_VERSIONS: null,
|
||||||
|
APP_GET_PATH: null,
|
||||||
|
APP_SET_BADGE_COUNT: null,
|
||||||
|
APP_DOCK_SET_BADGE: null,
|
||||||
|
APP_DOCK_BOUNCE: null,
|
||||||
|
APP_DOCK_CANCEL_BOUNCE: null,
|
||||||
|
APP_RELAUNCH: null,
|
||||||
|
APP_GET_DEFAULT_DOUBLE_CLICK_ACTION: null,
|
||||||
|
CLIPBOARD_COPY: null,
|
||||||
|
CLIPBOARD_CUT: null,
|
||||||
|
CLIPBOARD_PASTE: null,
|
||||||
|
CHECK_FOR_UPDATES: null,
|
||||||
|
CONSTANTS_GET: null,
|
||||||
|
CRASH_REPORTER_UPDATE_METADATA: null,
|
||||||
|
FEATURES_GET_BROWSER_FEATURES: null,
|
||||||
|
FILE_MANAGER_GET_MODULE_PATH: null,
|
||||||
|
FILE_MANAGER_GET_MODULE_DATA_PATH_SYNC: null,
|
||||||
|
FILE_MANAGER_SHOW_SAVE_DIALOG: null,
|
||||||
|
FILE_MANAGER_SHOW_OPEN_DIALOG: null,
|
||||||
|
FILE_MANAGER_SHOW_ITEM_IN_FOLDER: null,
|
||||||
|
GPU_SETTINGS_SET_ENABLE_HWACCEL: null,
|
||||||
|
GPU_SETTINGS_GET_ENABLE_HWACCEL_SYNC: null,
|
||||||
|
NATIVE_MODULES_GET_PATHS: null,
|
||||||
|
NATIVE_MODULES_INSTALL: null,
|
||||||
|
NATIVE_MODULES_FINISH_UPDATER_BOOTSTRAP: null,
|
||||||
|
NATIVE_MODULES_GET_HAS_NEW_UPDATER: null,
|
||||||
|
NOTIFICATION_CLOSE: null,
|
||||||
|
NOTIFICATION_SHOW: null,
|
||||||
|
NOTIFICATIONS_CLEAR: null,
|
||||||
|
OPEN_EXTERNAL_URL: null,
|
||||||
|
POWER_MONITOR_RESUME: null,
|
||||||
|
POWER_MONITOR_SUSPEND: null,
|
||||||
|
POWER_MONITOR_LOCK_SCREEN: null,
|
||||||
|
POWER_MONITOR_UNLOCK_SCREEN: null,
|
||||||
|
POWER_MONITOR_GET_SYSTEM_IDLE_TIME: null,
|
||||||
|
POWER_SAVE_BLOCKER_BLOCK_DISPLAY_SLEEP: null,
|
||||||
|
POWER_SAVE_BLOCKER_UNBLOCK_DISPLAY_SLEEP: null,
|
||||||
|
POWER_SAVE_BLOCKER_CLEANUP_DISPLAY_SLEEP: null,
|
||||||
|
PROCESS_UTILS_GET_CPU_USAGE: null,
|
||||||
|
PROCESS_UTILS_GET_MEMORY_INFO: null,
|
||||||
|
PROCESS_UTILS_FLUSH_DNS_CACHE: null,
|
||||||
|
PROCESS_UTILS_FLUSH_COOKIES: null,
|
||||||
|
PROCESS_UTILS_FLUSH_STORAGE_DATA: null,
|
||||||
|
PROCESS_UTILS_GET_MAIN_ARGV_SYNC: null,
|
||||||
|
QUIT_AND_INSTALL: null,
|
||||||
|
SETTINGS_GET: null,
|
||||||
|
SETTINGS_SET: null,
|
||||||
|
SETTINGS_GET_SYNC: null,
|
||||||
|
SETTINGS_UPDATE_BACKGROUND_COLOR: null,
|
||||||
|
SPELLCHECK_RESULT: null,
|
||||||
|
SPELLCHECK_REPLACE_MISSPELLING: null,
|
||||||
|
SPELLCHECK_GET_AVAILABLE_DICTIONARIES: null,
|
||||||
|
SPELLCHECK_SET_LOCALE: null,
|
||||||
|
SPELLCHECK_SET_LEARNED_WORDS: null,
|
||||||
|
SYSTEM_TRAY_SET_ICON: null,
|
||||||
|
SYSTEM_TRAY_SET_APPLICATIONS: null,
|
||||||
|
TOGGLE_MINIMIZE_TO_TRAY: null,
|
||||||
|
TOGGLE_OPEN_ON_STARTUP: null,
|
||||||
|
TOGGLE_START_MINIMIZED: null,
|
||||||
|
UPDATE_OPEN_ON_STARTUP: null,
|
||||||
|
UPDATER_HISTORY_QUERY_AND_TRUNCATE: null,
|
||||||
|
UPDATED_QUOTES: null,
|
||||||
|
USER_DATA_CACHE_DELETE: null,
|
||||||
|
USER_DATA_CACHE_GET: null,
|
||||||
|
USER_DATA_CACHE_SAVE: null,
|
||||||
|
WINDOW_BLUR: null,
|
||||||
|
WINDOW_CLOSE: null,
|
||||||
|
WINDOW_FOCUS: null,
|
||||||
|
WINDOW_MAXIMIZE: null,
|
||||||
|
WINDOW_MINIMIZE: null,
|
||||||
|
WINDOW_RESTORE: null,
|
||||||
|
WINDOW_FLASH_FRAME: null,
|
||||||
|
WINDOW_TOGGLE_FULLSCREEN: null,
|
||||||
|
WINDOW_SET_BACKGROUND_THROTTLING: null,
|
||||||
|
WINDOW_SET_PROGRESS_BAR: null,
|
||||||
|
WINDOW_IS_ALWAYS_ON_TOP: null,
|
||||||
|
WINDOW_SET_ALWAYS_ON_TOP: null,
|
||||||
|
WINDOW_DEVTOOLS_OPENED: null,
|
||||||
|
WINDOW_DEVTOOLS_CLOSED: null
|
||||||
|
});
|
||||||
|
exports.IPCEvents = IPCEvents;
|
15
app/discord_native/renderer/accessibility.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
ACCESSIBILITY_GET_ENABLED
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
async function isAccessibilitySupportEnabled() {
|
||||||
|
return electron.ipcRenderer.invoke(ACCESSIBILITY_GET_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isAccessibilitySupportEnabled
|
||||||
|
};
|
109
app/discord_native/renderer/app.js
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const allowedAppPaths = new Set(['home', 'appData', 'desktop', 'documents', 'downloads', 'crashDumps']);
|
||||||
|
|
||||||
|
const {
|
||||||
|
APP_GET_RELEASE_CHANNEL_SYNC,
|
||||||
|
APP_GET_HOST_VERSION_SYNC,
|
||||||
|
APP_GET_MODULE_VERSIONS,
|
||||||
|
APP_GET_PATH,
|
||||||
|
APP_SET_BADGE_COUNT,
|
||||||
|
APP_DOCK_SET_BADGE,
|
||||||
|
APP_DOCK_BOUNCE,
|
||||||
|
APP_DOCK_CANCEL_BOUNCE,
|
||||||
|
APP_RELAUNCH,
|
||||||
|
APP_GET_DEFAULT_DOUBLE_CLICK_ACTION
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let releaseChannel = electron.ipcRenderer.sendSync(APP_GET_RELEASE_CHANNEL_SYNC);
|
||||||
|
let hostVersion = electron.ipcRenderer.sendSync(APP_GET_HOST_VERSION_SYNC);
|
||||||
|
let moduleVersions = {};
|
||||||
|
electron.ipcRenderer.invoke(APP_GET_MODULE_VERSIONS).then(versions => {
|
||||||
|
moduleVersions = versions;
|
||||||
|
});
|
||||||
|
electron.ipcRenderer.on('DISCORD_MODULE_INSTALLED', async _ => {
|
||||||
|
moduleVersions = await electron.ipcRenderer.invoke(APP_GET_MODULE_VERSIONS);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getReleaseChannel() {
|
||||||
|
return releaseChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersion() {
|
||||||
|
return hostVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleVersions() {
|
||||||
|
return moduleVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPath(path) {
|
||||||
|
if (!allowedAppPaths.has(path)) {
|
||||||
|
throw new Error(`${path} is not an allowed app path`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return electron.ipcRenderer.invoke(APP_GET_PATH, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setBadgeCount(count) {
|
||||||
|
electron.ipcRenderer.invoke(APP_SET_BADGE_COUNT, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dockSetBadge(badge) {
|
||||||
|
electron.ipcRenderer.invoke(APP_DOCK_SET_BADGE, badge);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dockBounce(type) {
|
||||||
|
return electron.ipcRenderer.invoke(APP_DOCK_BOUNCE, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dockCancelBounce(id) {
|
||||||
|
electron.ipcRenderer.invoke(APP_DOCK_CANCEL_BOUNCE, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function relaunch() {
|
||||||
|
electron.ipcRenderer.invoke(APP_RELAUNCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDefaultDoubleClickAction() {
|
||||||
|
return electron.ipcRenderer.invoke(APP_GET_DEFAULT_DOUBLE_CLICK_ACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerUserInteractionHandler(elementId, eventType, callback) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
|
||||||
|
if (element == null) {
|
||||||
|
throw new Error(`Element with id '${elementId}' was not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUserInteraction(ev) {
|
||||||
|
if (!ev.isTrusted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.addEventListener(eventType, handleUserInteraction);
|
||||||
|
return () => {
|
||||||
|
element.removeEventListener(eventType, handleUserInteraction);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getReleaseChannel,
|
||||||
|
getVersion,
|
||||||
|
getModuleVersions,
|
||||||
|
getPath,
|
||||||
|
setBadgeCount,
|
||||||
|
dock: {
|
||||||
|
setBadge: dockSetBadge,
|
||||||
|
bounce: dockBounce,
|
||||||
|
cancelBounce: dockCancelBounce
|
||||||
|
},
|
||||||
|
relaunch,
|
||||||
|
getDefaultDoubleClickAction,
|
||||||
|
registerUserInteractionHandler
|
||||||
|
};
|
48
app/discord_native/renderer/clipboard.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const invariant = require('invariant');
|
||||||
|
|
||||||
|
const {
|
||||||
|
CLIPBOARD_COPY,
|
||||||
|
CLIPBOARD_CUT,
|
||||||
|
CLIPBOARD_PASTE
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
function copy(text) {
|
||||||
|
if (text) {
|
||||||
|
electron.clipboard.writeText(text);
|
||||||
|
} else {
|
||||||
|
electron.ipcRenderer.invoke(CLIPBOARD_COPY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyImage(imageArrayBuffer, imageSrc) {
|
||||||
|
invariant(imageArrayBuffer != null, 'Image data is empty');
|
||||||
|
const nativeImg = electron.nativeImage.createFromBuffer(imageArrayBuffer);
|
||||||
|
electron.clipboard.write({
|
||||||
|
html: `<img src="${imageSrc}">`,
|
||||||
|
image: nativeImg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cut() {
|
||||||
|
electron.ipcRenderer.invoke(CLIPBOARD_CUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function paste() {
|
||||||
|
electron.ipcRenderer.invoke(CLIPBOARD_PASTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
function read() {
|
||||||
|
return electron.clipboard.readText();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
copy,
|
||||||
|
copyImage,
|
||||||
|
cut,
|
||||||
|
paste,
|
||||||
|
read
|
||||||
|
};
|
38
app/discord_native/renderer/crashReporter.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
reconcileCrashReporterMetadata
|
||||||
|
} = require('../../../common/crashReporterUtils');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getElectronMajorVersion
|
||||||
|
} = require('../../../common/processUtils');
|
||||||
|
|
||||||
|
const {
|
||||||
|
CRASH_REPORTER_UPDATE_METADATA
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let metadata = {};
|
||||||
|
updateCrashReporter(metadata);
|
||||||
|
|
||||||
|
async function updateCrashReporter(additional_metadata) {
|
||||||
|
const result = await electron.ipcRenderer.invoke(CRASH_REPORTER_UPDATE_METADATA, additional_metadata); // Calling crashReporter.start from a renderer process was deprecated in Electron 9.
|
||||||
|
|
||||||
|
if (getElectronMajorVersion() < 9) {
|
||||||
|
electron.crashReporter.start(result.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = result.metadata || {};
|
||||||
|
reconcileCrashReporterMetadata(electron.crashReporter, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
updateCrashReporter,
|
||||||
|
getMetadata
|
||||||
|
};
|
21
app/discord_native/renderer/desktopCapture.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
function getDesktopCaptureSources(options) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
electron.desktopCapturer.getSources(options).then(sources => {
|
||||||
|
return resolve(sources.map(source => {
|
||||||
|
return {
|
||||||
|
id: source.id,
|
||||||
|
name: source.name,
|
||||||
|
url: source.thumbnail.toDataURL()
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getDesktopCaptureSources
|
||||||
|
};
|
22
app/discord_native/renderer/features.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FEATURES_GET_BROWSER_FEATURES
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let supportedFeatures = new Set(electron.ipcRenderer.sendSync(FEATURES_GET_BROWSER_FEATURES));
|
||||||
|
|
||||||
|
function supports(feature) {
|
||||||
|
return supportedFeatures.has(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
function declareSupported(feature) {
|
||||||
|
supportedFeatures.add(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
supports,
|
||||||
|
declareSupported
|
||||||
|
};
|
177
app/discord_native/renderer/fileManager.js
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const originalFs = require('original-fs');
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getPath
|
||||||
|
} = require('./app');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getElectronMajorVersion
|
||||||
|
} = require('../../../common/processUtils');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FILE_MANAGER_GET_MODULE_PATH,
|
||||||
|
FILE_MANAGER_GET_MODULE_DATA_PATH_SYNC,
|
||||||
|
FILE_MANAGER_SHOW_SAVE_DIALOG,
|
||||||
|
FILE_MANAGER_SHOW_OPEN_DIALOG,
|
||||||
|
FILE_MANAGER_SHOW_ITEM_IN_FOLDER
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
const INVALID_FILENAME_CHAR_REGEX = /[^a-zA-Z0-9-_.]/g;
|
||||||
|
const readdir = util.promisify(originalFs.readdir);
|
||||||
|
|
||||||
|
async function saveWithDialog(fileContents, fileName) {
|
||||||
|
if (INVALID_FILENAME_CHAR_REGEX.test(fileName)) {
|
||||||
|
throw new Error('fileName has invalid characters');
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPath = path.join((await getPath('downloads')), fileName);
|
||||||
|
const results = await electron.ipcRenderer.invoke(FILE_MANAGER_SHOW_SAVE_DIALOG, {
|
||||||
|
defaultPath
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results && results.filePath) {
|
||||||
|
fs.writeFileSync(results.filePath, fileContents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showOpenDialog({
|
||||||
|
filters,
|
||||||
|
properties
|
||||||
|
}) {
|
||||||
|
const results = await electron.ipcRenderer.invoke(FILE_MANAGER_SHOW_OPEN_DIALOG, {
|
||||||
|
filters,
|
||||||
|
properties
|
||||||
|
});
|
||||||
|
return results.filePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimes(filenames) {
|
||||||
|
return Promise.allSettled(filenames.map(filename => new Promise((resolve, reject) => {
|
||||||
|
originalFs.stat(filename, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stats.isFile()) {
|
||||||
|
return reject(new Error('Not a file'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve({
|
||||||
|
filename,
|
||||||
|
mtime: stats.mtime
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function orderedFiles(folder) {
|
||||||
|
try {
|
||||||
|
const filenames = await readdir(folder);
|
||||||
|
const times = await getTimes(filenames.map(filename => path.join(folder, filename)));
|
||||||
|
return times.filter(result => result.status === 'fulfilled').map(result => result.value).sort((a, b) => b.mtime.getTime() - a.mtime.getTime()).map(a => a.filename);
|
||||||
|
} catch (err) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readLogFiles(maxSize) {
|
||||||
|
const modulePath = await getModulePath();
|
||||||
|
const webrtcLog0 = path.join(modulePath, 'discord_voice', 'discord-webrtc_0');
|
||||||
|
const webrtcLog1 = path.join(modulePath, 'discord_voice', 'discord-webrtc_1');
|
||||||
|
const webrtcLog2 = path.join(modulePath, 'discord_voice', 'discord-last-webrtc_0');
|
||||||
|
const webrtcLog3 = path.join(modulePath, 'discord_voice', 'discord-last-webrtc_1');
|
||||||
|
const hookLog = path.join(modulePath, 'discord_hook', 'hook.log');
|
||||||
|
const audioState = path.join(modulePath, 'discord_voice', 'audio_state.json');
|
||||||
|
const filesToUpload = [webrtcLog0, webrtcLog1, webrtcLog2, webrtcLog3, hookLog, audioState]; // Electron 9 changes crash folder location
|
||||||
|
|
||||||
|
const crashBaseFolder = getElectronMajorVersion() < 9 ? path.join(os.tmpdir(), 'Discord Crashes') : await getPath('crashDumps');
|
||||||
|
const crashFolder = process.platform === 'win32' ? path.join(crashBaseFolder, 'reports') : path.join(crashBaseFolder, 'completed');
|
||||||
|
const crashFiles = await orderedFiles(crashFolder);
|
||||||
|
|
||||||
|
if (crashFiles.length > 0) {
|
||||||
|
filesToUpload.push(crashFiles[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await readFiles(filesToUpload, maxSize);
|
||||||
|
return files.filter(result => result.status === 'fulfilled').map(result => result.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showItemInFolder(path) {
|
||||||
|
electron.ipcRenderer.invoke(FILE_MANAGER_SHOW_ITEM_IN_FOLDER, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openFiles(dialogOptions, maxSize) {
|
||||||
|
const filenames = await showOpenDialog(dialogOptions);
|
||||||
|
|
||||||
|
if (filenames == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await readFiles(filenames, maxSize);
|
||||||
|
files.forEach(result => {
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
throw result.reason;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return files.map(result => result.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFiles(filenames, maxSize) {
|
||||||
|
return Promise.allSettled(filenames.map(filename => new Promise((resolve, reject) => {
|
||||||
|
originalFs.stat(filename, (err, stats) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
|
||||||
|
if (stats.size > maxSize) {
|
||||||
|
// Used to help determine why openFiles failed.
|
||||||
|
// Cannot use an error here because context bridge will remove the code field.
|
||||||
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
|
return reject({
|
||||||
|
code: 'ETOOLARGE',
|
||||||
|
message: 'upload too large'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
originalFs.readFile(filename, (err, data) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
return resolve({
|
||||||
|
data: data.buffer,
|
||||||
|
filename: path.basename(filename)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getModulePath() {
|
||||||
|
return electron.ipcRenderer.invoke(FILE_MANAGER_GET_MODULE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleDataPathSync() {
|
||||||
|
return electron.ipcRenderer.sendSync(FILE_MANAGER_GET_MODULE_DATA_PATH_SYNC);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
readLogFiles,
|
||||||
|
saveWithDialog,
|
||||||
|
openFiles,
|
||||||
|
showOpenDialog,
|
||||||
|
showItemInFolder,
|
||||||
|
getModulePath,
|
||||||
|
getModuleDataPathSync,
|
||||||
|
extname: path.extname,
|
||||||
|
basename: path.basename,
|
||||||
|
dirname: path.dirname,
|
||||||
|
join: path.join
|
||||||
|
};
|
23
app/discord_native/renderer/gpuSettings.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
GPU_SETTINGS_SET_ENABLE_HWACCEL,
|
||||||
|
GPU_SETTINGS_GET_ENABLE_HWACCEL_SYNC
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
const hardwareAccelerationEnabled = electron.ipcRenderer.sendSync(GPU_SETTINGS_GET_ENABLE_HWACCEL_SYNC);
|
||||||
|
|
||||||
|
function getEnableHardwareAcceleration() {
|
||||||
|
return hardwareAccelerationEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setEnableHardwareAcceleration(enable) {
|
||||||
|
electron.ipcRenderer.invoke(GPU_SETTINGS_SET_ENABLE_HWACCEL, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getEnableHardwareAcceleration,
|
||||||
|
setEnableHardwareAcceleration
|
||||||
|
};
|
111
app/discord_native/renderer/http.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
const {
|
||||||
|
CONSTANTS_GET
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
async function getAPIEndpoint() {
|
||||||
|
const apiEndpoint = await electron.ipcRenderer.invoke(CONSTANTS_GET, 'API_ENDPOINT');
|
||||||
|
|
||||||
|
if (apiEndpoint == null || apiEndpoint === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeChunkedRequest(route, chunks, options) {
|
||||||
|
/**
|
||||||
|
* Given an array of chunks, make a slow request, only writing chunks
|
||||||
|
* after a specified amount of time
|
||||||
|
*
|
||||||
|
* route: string
|
||||||
|
* options: object
|
||||||
|
* method: the method of the request
|
||||||
|
* contentType: the content type of the request
|
||||||
|
* chunkInterval: how long to wait to upload a chunk after the last chunk was flushed
|
||||||
|
* token: the token to make an authorized request from
|
||||||
|
* chunks: chunked body of the request to upload
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
method,
|
||||||
|
chunkInterval,
|
||||||
|
token,
|
||||||
|
contentType
|
||||||
|
} = options;
|
||||||
|
let httpModule = http;
|
||||||
|
|
||||||
|
if (route.startsWith('https')) {
|
||||||
|
httpModule = https;
|
||||||
|
} // we will force the URL to hit only API_ENDPOINT
|
||||||
|
|
||||||
|
|
||||||
|
const apiEndpoint = await getAPIEndpoint();
|
||||||
|
|
||||||
|
if (apiEndpoint == null) {
|
||||||
|
throw new Error('missing api endpoint setting');
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiEndpointUrl = new URL(apiEndpoint);
|
||||||
|
const url = new URL(route, apiEndpoint);
|
||||||
|
url.protocol = apiEndpointUrl.protocol;
|
||||||
|
url.host = apiEndpointUrl.host;
|
||||||
|
|
||||||
|
if (!url.pathname.startsWith(apiEndpointUrl.pathname)) {
|
||||||
|
url.pathname = `${apiEndpointUrl.pathname}${url.pathname}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let writeTimeout;
|
||||||
|
const req = httpModule.request(url.toString(), {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
authorization: token,
|
||||||
|
'Content-Type': contentType,
|
||||||
|
'Content-Length': Buffer.byteLength(chunks.join(''))
|
||||||
|
}
|
||||||
|
}, res => {
|
||||||
|
let responseData = '';
|
||||||
|
res.setEncoding('utf8');
|
||||||
|
res.on('data', chunk => {
|
||||||
|
responseData += chunk;
|
||||||
|
});
|
||||||
|
res.on('end', () => {
|
||||||
|
resolve({
|
||||||
|
status: res.statusCode,
|
||||||
|
body: responseData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', e => {
|
||||||
|
if (writeTimeout != null) {
|
||||||
|
clearTimeout(writeTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
req.write(chunks[i], () => {
|
||||||
|
writeTimeout = setTimeout(resolve, chunkInterval);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAPIEndpoint,
|
||||||
|
makeChunkedRequest: function (route, chunks, options, callback) {
|
||||||
|
makeChunkedRequest(route, chunks, options).then(body => callback(null, body)).catch(err => callback(err));
|
||||||
|
}
|
||||||
|
};
|
32
app/discord_native/renderer/ipc.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getDiscordIPCEvent,
|
||||||
|
IPCEvents
|
||||||
|
} = require('../common/constants');
|
||||||
|
|
||||||
|
const ipcRenderer = electron.ipcRenderer; // Sending ipc directly from the renderer is considered deprecated.
|
||||||
|
// App still sends a few, so we whitelist those.
|
||||||
|
|
||||||
|
const RENDERER_IPC_WHITELIST = [IPCEvents.APP_BADGE_SET, IPCEvents.CHECK_FOR_UPDATES, IPCEvents.NOTIFICATION_CLOSE, IPCEvents.NOTIFICATION_SHOW, IPCEvents.NOTIFICATIONS_CLEAR, IPCEvents.OPEN_EXTERNAL_URL, IPCEvents.QUIT_AND_INSTALL, IPCEvents.SETTINGS_UPDATE_BACKGROUND_COLOR, IPCEvents.SYSTEM_TRAY_SET_ICON, IPCEvents.SYSTEM_TRAY_SET_APPLICATIONS, IPCEvents.TOGGLE_MINIMIZE_TO_TRAY, IPCEvents.TOGGLE_OPEN_ON_STARTUP, IPCEvents.TOGGLE_START_MINIMIZED, IPCEvents.UPDATE_OPEN_ON_STARTUP, IPCEvents.UPDATER_HISTORY_QUERY_AND_TRUNCATE, IPCEvents.UPDATED_QUOTES];
|
||||||
|
|
||||||
|
function send(ev, ...args) {
|
||||||
|
const prefixedEvent = getDiscordIPCEvent(ev);
|
||||||
|
|
||||||
|
if (!RENDERER_IPC_WHITELIST.includes(prefixedEvent)) {
|
||||||
|
throw new Error('cannot send this event');
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send(prefixedEvent, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function on(ev, callback) {
|
||||||
|
ipcRenderer.on(getDiscordIPCEvent(ev), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
send,
|
||||||
|
on
|
||||||
|
};
|
59
app/discord_native/renderer/nativeModules.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
NATIVE_MODULES_GET_PATHS,
|
||||||
|
NATIVE_MODULES_INSTALL,
|
||||||
|
NATIVE_MODULES_GET_HAS_NEW_UPDATER
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
const modulePromises = {};
|
||||||
|
|
||||||
|
function getSanitizedModulePaths() {
|
||||||
|
let sanitizedModulePaths = [];
|
||||||
|
const {
|
||||||
|
mainAppDirname,
|
||||||
|
browserModulePaths
|
||||||
|
} = electron.ipcRenderer.sendSync(NATIVE_MODULES_GET_PATHS);
|
||||||
|
browserModulePaths.forEach(modulePath => {
|
||||||
|
if (!modulePath.includes('electron.asar')) {
|
||||||
|
sanitizedModulePaths.push(modulePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rendererModulePaths = require('module')._nodeModulePaths(mainAppDirname);
|
||||||
|
|
||||||
|
sanitizedModulePaths = sanitizedModulePaths.concat(rendererModulePaths.slice(0, 2));
|
||||||
|
return sanitizedModulePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHasNewUpdater() {
|
||||||
|
return electron.ipcRenderer.sendSync(NATIVE_MODULES_GET_HAS_NEW_UPDATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureModule(name) {
|
||||||
|
let modulePromise = modulePromises[name];
|
||||||
|
|
||||||
|
if (modulePromise == null) {
|
||||||
|
modulePromise = electron.ipcRenderer.invoke(NATIVE_MODULES_INSTALL, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
await modulePromise;
|
||||||
|
module.paths = getSanitizedModulePaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireModule(name) {
|
||||||
|
if (!/^discord_[a-z0-9_-]+$/.test(name) && name !== 'erlpack') {
|
||||||
|
throw new Error('"' + String(name) + '" is not a whitelisted native module');
|
||||||
|
}
|
||||||
|
|
||||||
|
return require(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.paths = getSanitizedModulePaths();
|
||||||
|
module.exports = {
|
||||||
|
ensureModule,
|
||||||
|
requireModule,
|
||||||
|
canBootstrapNewUpdater: !getHasNewUpdater()
|
||||||
|
};
|
16
app/discord_native/renderer/os.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
let arch = os.arch();
|
||||||
|
|
||||||
|
if (process.platform === 'win32' && process.env['PROCESSOR_ARCHITEW6432'] != null) {
|
||||||
|
arch = 'x64';
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
release: os.release(),
|
||||||
|
arch
|
||||||
|
};
|
50
app/discord_native/renderer/powerMonitor.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
|
||||||
|
const {
|
||||||
|
POWER_MONITOR_RESUME,
|
||||||
|
POWER_MONITOR_SUSPEND,
|
||||||
|
POWER_MONITOR_LOCK_SCREEN,
|
||||||
|
POWER_MONITOR_UNLOCK_SCREEN,
|
||||||
|
POWER_MONITOR_GET_SYSTEM_IDLE_TIME
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
const events = new EventEmitter();
|
||||||
|
electron.ipcRenderer.on(POWER_MONITOR_RESUME, () => {
|
||||||
|
events.emit('resume');
|
||||||
|
});
|
||||||
|
electron.ipcRenderer.on(POWER_MONITOR_SUSPEND, () => {
|
||||||
|
events.emit('suspend');
|
||||||
|
});
|
||||||
|
electron.ipcRenderer.on(POWER_MONITOR_LOCK_SCREEN, () => {
|
||||||
|
events.emit('lock-screen');
|
||||||
|
});
|
||||||
|
electron.ipcRenderer.on(POWER_MONITOR_UNLOCK_SCREEN, () => {
|
||||||
|
events.emit('unlock-screen');
|
||||||
|
});
|
||||||
|
|
||||||
|
function on() {
|
||||||
|
events.on.apply(events, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeListener() {
|
||||||
|
events.removeListener.apply(events, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAllListeners() {
|
||||||
|
events.removeAllListeners.apply(events, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSystemIdleTimeMs() {
|
||||||
|
return electron.ipcRenderer.invoke(POWER_MONITOR_GET_SYSTEM_IDLE_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
on,
|
||||||
|
removeListener,
|
||||||
|
removeAllListeners,
|
||||||
|
getSystemIdleTimeMs
|
||||||
|
};
|
27
app/discord_native/renderer/powerSaveBlocker.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
POWER_SAVE_BLOCKER_BLOCK_DISPLAY_SLEEP,
|
||||||
|
POWER_SAVE_BLOCKER_UNBLOCK_DISPLAY_SLEEP,
|
||||||
|
POWER_SAVE_BLOCKER_CLEANUP_DISPLAY_SLEEP
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
async function blockDisplaySleep() {
|
||||||
|
return electron.ipcRenderer.invoke(POWER_SAVE_BLOCKER_BLOCK_DISPLAY_SLEEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unblockDisplaySleep(id) {
|
||||||
|
return electron.ipcRenderer.invoke(POWER_SAVE_BLOCKER_UNBLOCK_DISPLAY_SLEEP, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanupDisplaySleep() {
|
||||||
|
return electron.ipcRenderer.invoke(POWER_SAVE_BLOCKER_CLEANUP_DISPLAY_SLEEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
blockDisplaySleep,
|
||||||
|
unblockDisplaySleep,
|
||||||
|
cleanupDisplaySleep
|
||||||
|
};
|
18
app/discord_native/renderer/process.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
const env = process.env;
|
||||||
|
module.exports = {
|
||||||
|
platform: process.platform,
|
||||||
|
arch: process.arch,
|
||||||
|
env: {
|
||||||
|
LOCALAPPDATA: env['LOCALAPPDATA'],
|
||||||
|
'PROGRAMFILES(X86)': env['PROGRAMFILES(X86)'],
|
||||||
|
PROGRAMFILES: env['PROGRAMFILES'],
|
||||||
|
PROGRAMW6432: env['PROGRAMW6432'],
|
||||||
|
PROGRAMDATA: env['PROGRAMDATA']
|
||||||
|
}
|
||||||
|
};
|
76
app/discord_native/renderer/processUtils.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
const {
|
||||||
|
PROCESS_UTILS_GET_CPU_USAGE,
|
||||||
|
PROCESS_UTILS_GET_MEMORY_INFO,
|
||||||
|
PROCESS_UTILS_FLUSH_DNS_CACHE,
|
||||||
|
PROCESS_UTILS_FLUSH_COOKIES,
|
||||||
|
PROCESS_UTILS_FLUSH_STORAGE_DATA,
|
||||||
|
PROCESS_UTILS_GET_MAIN_ARGV_SYNC
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
const CPU_USAGE_GATHER_INTERVAL = 1000;
|
||||||
|
const MEMORY_USAGE_GATHER_INTERVAL = 5000;
|
||||||
|
const mainArgv = electron.ipcRenderer.sendSync(PROCESS_UTILS_GET_MAIN_ARGV_SYNC);
|
||||||
|
let totalProcessorUsagePercent = 0;
|
||||||
|
let totalMemoryUsageKB = 0;
|
||||||
|
setInterval(() => {
|
||||||
|
electron.ipcRenderer.invoke(PROCESS_UTILS_GET_CPU_USAGE).then(usage => totalProcessorUsagePercent = usage);
|
||||||
|
}, CPU_USAGE_GATHER_INTERVAL);
|
||||||
|
setInterval(() => {
|
||||||
|
Promise.all([process.getProcessMemoryInfo(), electron.ipcRenderer.invoke(PROCESS_UTILS_GET_MEMORY_INFO)].map(x => x.catch(() => 0))).then(usages => {
|
||||||
|
totalMemoryUsageKB = usages.reduce((total, usage) => total + usage.private, 0);
|
||||||
|
});
|
||||||
|
}, MEMORY_USAGE_GATHER_INTERVAL);
|
||||||
|
|
||||||
|
async function flushDNSCache() {
|
||||||
|
electron.ipcRenderer.invoke(PROCESS_UTILS_FLUSH_DNS_CACHE);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushCookies(callback) {
|
||||||
|
try {
|
||||||
|
await electron.ipcRenderer.invoke(PROCESS_UTILS_FLUSH_COOKIES);
|
||||||
|
callback();
|
||||||
|
} catch (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushStorageData(callback) {
|
||||||
|
try {
|
||||||
|
await electron.ipcRenderer.invoke(PROCESS_UTILS_FLUSH_STORAGE_DATA);
|
||||||
|
callback();
|
||||||
|
} catch (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function purgeMemory() {
|
||||||
|
electron.webFrame.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentCPUUsagePercent() {
|
||||||
|
return totalProcessorUsagePercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMemoryUsageKB() {
|
||||||
|
return totalMemoryUsageKB;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMainArgvSync() {
|
||||||
|
return mainArgv;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
flushDNSCache,
|
||||||
|
flushCookies,
|
||||||
|
flushStorageData,
|
||||||
|
purgeMemory,
|
||||||
|
getCurrentCPUUsagePercent,
|
||||||
|
getCurrentMemoryUsageKB,
|
||||||
|
getMainArgvSync
|
||||||
|
};
|
35
app/discord_native/renderer/settings.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
SETTINGS_GET,
|
||||||
|
SETTINGS_SET,
|
||||||
|
SETTINGS_GET_SYNC
|
||||||
|
} = require('../common/constants').IPCEvents; // Updating app settings directly from the renderer is considered deprecated.
|
||||||
|
// Voice still sets a few options, so we whitelist those.
|
||||||
|
|
||||||
|
|
||||||
|
const RENDERER_SET_WHITELIST = ['audioSubsystem', 'useLegacyAudioDevice', 'debugLogging'];
|
||||||
|
|
||||||
|
async function get(name, defaultValue) {
|
||||||
|
return electron.ipcRenderer.invoke(SETTINGS_GET, name, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function set(name, value) {
|
||||||
|
if (!RENDERER_SET_WHITELIST.includes(name)) {
|
||||||
|
throw new Error('cannot set this setting key');
|
||||||
|
}
|
||||||
|
|
||||||
|
return electron.ipcRenderer.invoke(SETTINGS_SET, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSync(name, defaultValue) {
|
||||||
|
return electron.ipcRenderer.sendSync(SETTINGS_GET_SYNC, name, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
getSync
|
||||||
|
};
|
83
app/discord_native/renderer/spellCheck.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getElectronMajorVersion
|
||||||
|
} = require('../../../common/processUtils');
|
||||||
|
|
||||||
|
const {
|
||||||
|
SPELLCHECK_RESULT,
|
||||||
|
SPELLCHECK_REPLACE_MISSPELLING,
|
||||||
|
SPELLCHECK_GET_AVAILABLE_DICTIONARIES,
|
||||||
|
SPELLCHECK_SET_LOCALE,
|
||||||
|
SPELLCHECK_SET_LEARNED_WORDS
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
if (getElectronMajorVersion() < 8) {
|
||||||
|
async function setSpellCheckProvider(locale, autoCorrectWord, provider) {
|
||||||
|
const asyncProvider = {
|
||||||
|
spellCheck: (words, callback) => callback(words.filter(word => !provider.spellCheck(word)))
|
||||||
|
};
|
||||||
|
electron.webFrame.setSpellCheckProvider(locale, asyncProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function replaceMisspelling(word) {
|
||||||
|
electron.ipcRenderer.invoke(SPELLCHECK_REPLACE_MISSPELLING, word);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
setSpellCheckProvider,
|
||||||
|
replaceMisspelling
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const events = new EventEmitter();
|
||||||
|
electron.ipcRenderer.on(SPELLCHECK_RESULT, handleSpellcheckData);
|
||||||
|
|
||||||
|
function handleSpellcheckData(_, misspelledWord, dictionarySuggestions) {
|
||||||
|
events.emit('spellcheck-result', misspelledWord, dictionarySuggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function on() {
|
||||||
|
events.on.apply(events, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeListener() {
|
||||||
|
events.removeListener.apply(events, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAvailableDictionaries() {
|
||||||
|
return electron.ipcRenderer.invoke(SPELLCHECK_GET_AVAILABLE_DICTIONARIES);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setLocale(locale) {
|
||||||
|
let succeeded = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await electron.ipcRenderer.invoke(SPELLCHECK_SET_LOCALE, locale);
|
||||||
|
} catch (_) {
|
||||||
|
succeeded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setLearnedWords(learnedWords) {
|
||||||
|
return electron.ipcRenderer.invoke(SPELLCHECK_SET_LEARNED_WORDS, learnedWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function replaceMisspelling(correction) {
|
||||||
|
return electron.ipcRenderer.invoke(SPELLCHECK_REPLACE_MISSPELLING, correction);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
on,
|
||||||
|
removeListener,
|
||||||
|
getAvailableDictionaries,
|
||||||
|
setLocale,
|
||||||
|
setLearnedWords,
|
||||||
|
replaceMisspelling
|
||||||
|
};
|
||||||
|
}
|
28
app/discord_native/renderer/userDataCache.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
USER_DATA_CACHE_SAVE,
|
||||||
|
USER_DATA_CACHE_GET,
|
||||||
|
USER_DATA_CACHE_DELETE
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
async function getCached() {
|
||||||
|
const cached = await electron.ipcRenderer.invoke(USER_DATA_CACHE_GET);
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheUserData(userData) {
|
||||||
|
electron.ipcRenderer.send(USER_DATA_CACHE_SAVE, userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCache() {
|
||||||
|
electron.ipcRenderer.send(USER_DATA_CACHE_DELETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getCached,
|
||||||
|
cacheUserData,
|
||||||
|
deleteCache
|
||||||
|
};
|
110
app/discord_native/renderer/window.js
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const {
|
||||||
|
WINDOW_BLUR,
|
||||||
|
WINDOW_CLOSE,
|
||||||
|
WINDOW_FOCUS,
|
||||||
|
WINDOW_MAXIMIZE,
|
||||||
|
WINDOW_MINIMIZE,
|
||||||
|
WINDOW_RESTORE,
|
||||||
|
WINDOW_FLASH_FRAME,
|
||||||
|
WINDOW_TOGGLE_FULLSCREEN,
|
||||||
|
WINDOW_SET_BACKGROUND_THROTTLING,
|
||||||
|
WINDOW_SET_PROGRESS_BAR,
|
||||||
|
WINDOW_IS_ALWAYS_ON_TOP,
|
||||||
|
WINDOW_SET_ALWAYS_ON_TOP,
|
||||||
|
WINDOW_DEVTOOLS_OPENED,
|
||||||
|
WINDOW_DEVTOOLS_CLOSED
|
||||||
|
} = require('../common/constants').IPCEvents;
|
||||||
|
|
||||||
|
let devtoolsOpenedCallback = () => {};
|
||||||
|
|
||||||
|
let devtoolsClosedCallback = () => {};
|
||||||
|
|
||||||
|
electron.ipcRenderer.on(WINDOW_DEVTOOLS_OPENED, async _ => {
|
||||||
|
if (devtoolsOpenedCallback != null) {
|
||||||
|
devtoolsOpenedCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcRenderer.on(WINDOW_DEVTOOLS_CLOSED, async _ => {
|
||||||
|
if (devtoolsOpenedCallback != null) {
|
||||||
|
devtoolsOpenedCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function flashFrame(flag) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_FLASH_FRAME, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function minimize(key) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_MINIMIZE, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restore(key) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_RESTORE, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function maximize(key) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_MAXIMIZE, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function focus(_hack, key) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_FOCUS, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setAlwaysOnTop(key, enabled) {
|
||||||
|
return electron.ipcRenderer.invoke(WINDOW_SET_ALWAYS_ON_TOP, key, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isAlwaysOnTop(key) {
|
||||||
|
return electron.ipcRenderer.invoke(WINDOW_IS_ALWAYS_ON_TOP, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function blur(key) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_BLUR, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setProgressBar(progress, key) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_SET_PROGRESS_BAR, key, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fullscreen(key) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_TOGGLE_FULLSCREEN, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function close(key) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_CLOSE, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setZoomFactor(factor) {
|
||||||
|
if (!electron.webFrame.setZoomFactor) return;
|
||||||
|
electron.webFrame.setZoomFactor(factor / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setBackgroundThrottling(enabled) {
|
||||||
|
electron.ipcRenderer.invoke(WINDOW_SET_BACKGROUND_THROTTLING, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setDevtoolsCallbacks(onOpened, onClosed) {
|
||||||
|
devtoolsOpenedCallback = onOpened;
|
||||||
|
devtoolsClosedCallback = onClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
flashFrame,
|
||||||
|
minimize,
|
||||||
|
restore,
|
||||||
|
maximize,
|
||||||
|
focus,
|
||||||
|
blur,
|
||||||
|
fullscreen,
|
||||||
|
close,
|
||||||
|
setAlwaysOnTop,
|
||||||
|
isAlwaysOnTop,
|
||||||
|
setZoomFactor,
|
||||||
|
setBackgroundThrottling,
|
||||||
|
setProgressBar,
|
||||||
|
setDevtoolsCallbacks
|
||||||
|
};
|
BIN
app/images/badges/badge-1.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-10.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-11.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-2.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-3.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-4.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-5.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-6.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-7.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-8.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/images/badges/badge-9.ico
Normal file
After Width: | Height: | Size: 15 KiB |
15
app/images/close.svg
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px"
|
||||||
|
viewBox="40 40 20 20" enable-background="new 40 40 20 20" xml:space="preserve">
|
||||||
|
<title>Slice 1</title>
|
||||||
|
<description>Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
|
||||||
|
<g id="Page_1">
|
||||||
|
<path id="Close" fill="#99aab5" d="M40.445,42.853l2.408-2.408c0.593-0.593,1.555-0.593,2.147,0l5,5l5-5
|
||||||
|
c0.593-0.593,1.554-0.593,2.147,0l2.408,2.408c0.593,0.593,0.593,1.554,0,2.147l-5,5l5,5c0.593,0.593,0.592,1.554,0,2.147
|
||||||
|
l-2.408,2.408c-0.593,0.593-1.555,0.593-2.147,0l-5-5l-5,5c-0.593,0.593-1.554,0.593-2.147,0l-2.408-2.408
|
||||||
|
c-0.593-0.593-0.593-1.554,0-2.147l5-5l-5-5C39.852,44.407,39.852,43.445,40.445,42.853z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
34
app/images/discord.svg
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 168 190" enable-background="new 0 0 168 190" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect x="14.4" y="46.1" fill="#FFFFFF" width="139.2" height="97.7"/>
|
||||||
|
<g>
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="66.6616" y1="79.7119" x2="66.6616" y2="100.7434">
|
||||||
|
<stop offset="0" style="stop-color:#7491D5"/>
|
||||||
|
<stop offset="1" style="stop-color:#4E68A0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_1_)" d="M66.7,79.7c-5.4,0-9.8,4.7-9.8,10.5c0,5.8,4.4,10.5,9.8,10.5c5.4,0,9.8-4.7,9.8-10.5
|
||||||
|
C76.5,84.4,72.1,79.7,66.7,79.7z"/>
|
||||||
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="101.661" y1="79.7119" x2="101.661" y2="100.7434">
|
||||||
|
<stop offset="0" style="stop-color:#7491D5"/>
|
||||||
|
<stop offset="1" style="stop-color:#4E68A0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_2_)" d="M101.7,79.7c-5.4,0-9.8,4.7-9.8,10.5c0,5.8,4.4,10.5,9.8,10.5c5.4,0,9.8-4.7,9.8-10.5
|
||||||
|
C111.5,84.4,107.1,79.7,101.7,79.7z"/>
|
||||||
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="84" y1="-4.545736e-07" x2="84" y2="190">
|
||||||
|
<stop offset="0" style="stop-color:#7491D5"/>
|
||||||
|
<stop offset="1" style="stop-color:#4E68A0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_3_)" d="M148.4,0H19.6C8.8,0,0,8.8,0,19.6v128.4c0,10.8,8.8,19.6,19.6,19.6h108.9l-5.1-17.5l12.3,11.3
|
||||||
|
l11.6,10.7L168,190v-41.9v-9.5V19.6C168,8.8,159.2,0,148.4,0z M111.3,124.1c0,0-3.4-4.1-6.3-7.7c12.6-3.5,17.4-11.3,17.4-11.3
|
||||||
|
c-4,2.6-7.7,4.4-11.1,5.6c-4.8,2-9.5,3.3-14,4.1c-9.2,1.7-17.6,1.3-24.9-0.1c-5.5-1-10.2-2.5-14.1-4.1c-2.2-0.8-4.6-1.9-7.1-3.3
|
||||||
|
c-0.3-0.2-0.6-0.3-0.9-0.5c-0.1-0.1-0.3-0.2-0.4-0.2c-1.7-1-2.6-1.6-2.6-1.6s4.6,7.6,16.8,11.2c-2.9,3.6-6.4,7.9-6.4,7.9
|
||||||
|
c-21.2-0.6-29.3-14.5-29.3-14.5c0-30.6,13.8-55.4,13.8-55.4c13.8-10.3,26.9-10,26.9-10l1,1.1C52.8,50.3,45,57.9,45,57.9
|
||||||
|
s2.1-1.2,5.7-2.7c10.3-4.5,18.4-5.7,21.8-6c0.5-0.1,1.1-0.2,1.6-0.2c5.9-0.7,12.5-0.9,19.4-0.2c9.1,1,18.9,3.7,28.9,9.1
|
||||||
|
c0,0-7.5-7.2-23.9-12.1l1.3-1.5c0,0,13.1-0.3,26.9,10c0,0,13.8,24.8,13.8,55.4C140.6,109.6,132.5,123.5,111.3,124.1z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/images/systemtray/darwin/tray-connectedTemplate.png
Normal file
After Width: | Height: | Size: 153 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@1.25x.png
Normal file
After Width: | Height: | Size: 189 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@1.33x.png
Normal file
After Width: | Height: | Size: 232 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@1.4x.png
Normal file
After Width: | Height: | Size: 238 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@1.5x.png
Normal file
After Width: | Height: | Size: 205 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@1.8x.png
Normal file
After Width: | Height: | Size: 281 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@2.5x.png
Normal file
After Width: | Height: | Size: 294 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@2x.png
Normal file
After Width: | Height: | Size: 229 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@3x.png
Normal file
After Width: | Height: | Size: 301 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@4x.png
Normal file
After Width: | Height: | Size: 421 B |
BIN
app/images/systemtray/darwin/tray-connectedTemplate@5x.png
Normal file
After Width: | Height: | Size: 540 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate.png
Normal file
After Width: | Height: | Size: 233 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@1.25x.png
Normal file
After Width: | Height: | Size: 291 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@1.33x.png
Normal file
After Width: | Height: | Size: 308 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@1.4x.png
Normal file
After Width: | Height: | Size: 313 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@1.5x.png
Normal file
After Width: | Height: | Size: 305 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@1.8x.png
Normal file
After Width: | Height: | Size: 383 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@2.5x.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@2x.png
Normal file
After Width: | Height: | Size: 385 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@3x.png
Normal file
After Width: | Height: | Size: 491 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@4x.png
Normal file
After Width: | Height: | Size: 668 B |
BIN
app/images/systemtray/darwin/tray-deafenedTemplate@5x.png
Normal file
After Width: | Height: | Size: 834 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate.png
Normal file
After Width: | Height: | Size: 216 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@1.25x.png
Normal file
After Width: | Height: | Size: 271 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@1.33x.png
Normal file
After Width: | Height: | Size: 284 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@1.4x.png
Normal file
After Width: | Height: | Size: 298 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@1.5x.png
Normal file
After Width: | Height: | Size: 293 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@1.8x.png
Normal file
After Width: | Height: | Size: 368 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@2.5x.png
Normal file
After Width: | Height: | Size: 454 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@2x.png
Normal file
After Width: | Height: | Size: 354 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@3x.png
Normal file
After Width: | Height: | Size: 496 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@4x.png
Normal file
After Width: | Height: | Size: 609 B |
BIN
app/images/systemtray/darwin/tray-mutedTemplate@5x.png
Normal file
After Width: | Height: | Size: 753 B |
BIN
app/images/systemtray/darwin/tray-speakingTemplate.png
Normal file
After Width: | Height: | Size: 199 B |
BIN
app/images/systemtray/darwin/tray-speakingTemplate@1.25x.png
Normal file
After Width: | Height: | Size: 261 B |