2019-09-16 22:57:56 +02:00
|
|
|
import { Events } from 'framework/events';
|
2021-03-08 22:29:59 +01:00
|
|
|
import { AutoTypeHelper } from 'auto-type/auto-type-helper';
|
2019-09-15 14:16:32 +02:00
|
|
|
import { AutoTypeParser } from 'auto-type/auto-type-parser';
|
2021-04-28 11:29:48 +02:00
|
|
|
import { SelectEntryFilter } from 'comp/app/select-entry-filter';
|
2019-09-15 14:16:32 +02:00
|
|
|
import { Launcher } from 'comp/launcher';
|
2021-02-04 20:54:13 +01:00
|
|
|
import { Features } from 'util/features';
|
2019-09-15 14:16:32 +02:00
|
|
|
import { Alerts } from 'comp/ui/alerts';
|
|
|
|
import { Timeouts } from 'const/timeouts';
|
|
|
|
import { AppSettingsModel } from 'models/app-settings-model';
|
2020-05-05 16:34:39 +02:00
|
|
|
import { AppModel } from 'models/app-model';
|
2019-09-15 14:16:32 +02:00
|
|
|
import { Locale } from 'util/locale';
|
|
|
|
import { Logger } from 'util/logger';
|
2021-02-04 20:54:13 +01:00
|
|
|
import { Links } from 'const/links';
|
2021-04-28 09:44:02 +02:00
|
|
|
import { SelectEntryView } from 'views/select/select-entry-view';
|
2017-01-31 07:50:28 +01:00
|
|
|
|
|
|
|
const logger = new Logger('auto-type');
|
2019-10-12 08:20:44 +02:00
|
|
|
const clearTextAutoTypeLog = !!localStorage.debugAutoType;
|
2017-01-31 07:50:28 +01:00
|
|
|
|
|
|
|
const AutoType = {
|
2017-01-31 23:09:35 +01:00
|
|
|
enabled: !!(Launcher && Launcher.autoTypeSupported),
|
2020-05-22 12:30:01 +02:00
|
|
|
supportsEventsWithWindowId: !!(Launcher && Launcher.platform() === 'linux'),
|
2016-07-24 22:57:12 +02:00
|
|
|
selectEntryView: false,
|
2016-08-18 22:01:08 +02:00
|
|
|
running: false,
|
2016-04-23 16:50:40 +02:00
|
|
|
|
2020-05-05 16:34:39 +02:00
|
|
|
init() {
|
2016-07-24 22:57:12 +02:00
|
|
|
if (!this.enabled) {
|
|
|
|
return;
|
|
|
|
}
|
2020-06-01 16:53:51 +02:00
|
|
|
Events.on('auto-type', (e) => this.handleEvent(e));
|
2016-07-24 19:11:25 +02:00
|
|
|
},
|
|
|
|
|
2016-08-07 19:34:31 +02:00
|
|
|
handleEvent(e) {
|
2019-08-16 23:05:39 +02:00
|
|
|
const entry = (e && e.entry) || null;
|
2019-09-26 23:43:07 +02:00
|
|
|
const sequence = (e && e.sequence) || null;
|
2020-05-09 10:37:34 +02:00
|
|
|
const context = (e && e.context) || null;
|
2016-07-24 19:11:25 +02:00
|
|
|
logger.debug('Auto type event', entry);
|
2016-08-18 22:01:08 +02:00
|
|
|
if (this.running) {
|
|
|
|
logger.debug('Already running, skipping event');
|
|
|
|
return;
|
|
|
|
}
|
2016-07-24 19:11:25 +02:00
|
|
|
if (entry) {
|
2019-08-16 23:05:39 +02:00
|
|
|
this.hideWindow(() => {
|
2020-05-09 10:37:34 +02:00
|
|
|
this.runAndHandleResult({ entry, sequence, context });
|
2019-08-16 23:05:39 +02:00
|
|
|
});
|
2016-07-24 19:11:25 +02:00
|
|
|
} else {
|
|
|
|
if (this.selectEntryView) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (Launcher.isAppFocused()) {
|
|
|
|
return Alerts.error({
|
|
|
|
header: Locale.autoTypeError,
|
|
|
|
body: Locale.autoTypeErrorGlobal,
|
|
|
|
skipIfAlertDisplayed: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
this.selectEntryAndRun();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-05-21 21:36:26 +02:00
|
|
|
runAndHandleResult(result, windowId) {
|
2020-06-01 16:53:51 +02:00
|
|
|
this.run(result, windowId, (err) => {
|
2016-07-24 19:11:25 +02:00
|
|
|
if (err) {
|
2021-02-04 20:54:13 +01:00
|
|
|
let body = Locale.autoTypeErrorGeneric.replace('{}', err.message || err.toString());
|
|
|
|
let link;
|
|
|
|
if (err.keyPressFailed && Features.isMac) {
|
|
|
|
body = Locale.autoTypeErrorAccessibilityMacOS;
|
|
|
|
link = Links.AutoTypeMacOS;
|
|
|
|
}
|
2016-07-24 19:11:25 +02:00
|
|
|
Alerts.error({
|
|
|
|
header: Locale.autoTypeError,
|
2021-02-04 20:54:13 +01:00
|
|
|
body,
|
|
|
|
link
|
2016-07-24 19:11:25 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2017-04-09 10:31:05 +02:00
|
|
|
|
2019-09-17 19:50:42 +02:00
|
|
|
if (AppSettingsModel.lockOnAutoType) {
|
2019-09-16 22:57:56 +02:00
|
|
|
Events.emit('lock-workspace');
|
2017-04-09 10:31:05 +02:00
|
|
|
}
|
2016-07-24 19:11:25 +02:00
|
|
|
},
|
|
|
|
|
2020-05-21 21:36:26 +02:00
|
|
|
run(result, windowId, callback) {
|
2016-08-18 22:01:08 +02:00
|
|
|
this.running = true;
|
2019-09-12 19:59:35 +02:00
|
|
|
const sequence = result.sequence || result.entry.getEffectiveAutoTypeSeq();
|
2020-05-09 10:37:34 +02:00
|
|
|
const context = result.context;
|
2016-04-08 17:40:00 +02:00
|
|
|
logger.debug('Start', sequence);
|
2017-01-31 07:50:28 +01:00
|
|
|
const ts = logger.ts();
|
2016-04-08 17:40:00 +02:00
|
|
|
try {
|
2017-01-31 07:50:28 +01:00
|
|
|
const parser = new AutoTypeParser(sequence);
|
|
|
|
const runner = parser.parse();
|
2016-07-17 13:30:38 +02:00
|
|
|
logger.debug('Parsed', this.printOps(runner.ops));
|
2020-06-01 16:53:51 +02:00
|
|
|
runner.resolve(result.entry, context, (err) => {
|
2016-04-08 17:40:00 +02:00
|
|
|
if (err) {
|
2016-08-18 22:01:08 +02:00
|
|
|
this.running = false;
|
2016-04-08 23:14:09 +02:00
|
|
|
logger.error('Resolve error', err);
|
2016-04-23 17:05:33 +02:00
|
|
|
return callback && callback(err);
|
2016-04-08 17:40:00 +02:00
|
|
|
}
|
2016-07-17 13:30:38 +02:00
|
|
|
logger.debug('Resolved', this.printOps(runner.ops));
|
2018-10-13 11:04:58 +02:00
|
|
|
if (result.entry.autoTypeObfuscation) {
|
2016-04-09 14:55:27 +02:00
|
|
|
try {
|
|
|
|
runner.obfuscate();
|
|
|
|
} catch (e) {
|
2016-08-18 22:01:08 +02:00
|
|
|
this.running = false;
|
2016-04-09 14:55:27 +02:00
|
|
|
logger.error('Obfuscate error', e);
|
2016-04-23 17:05:33 +02:00
|
|
|
return callback && callback(e);
|
2016-04-09 14:55:27 +02:00
|
|
|
}
|
2016-04-10 09:31:08 +02:00
|
|
|
logger.debug('Obfuscated');
|
2016-04-09 10:41:52 +02:00
|
|
|
}
|
2020-06-01 16:53:51 +02:00
|
|
|
runner.run((err) => {
|
2016-08-18 22:01:08 +02:00
|
|
|
this.running = false;
|
2016-04-08 17:40:00 +02:00
|
|
|
if (err) {
|
|
|
|
logger.error('Run error', err);
|
2016-04-23 17:05:33 +02:00
|
|
|
return callback && callback(err);
|
2016-04-08 17:40:00 +02:00
|
|
|
}
|
2016-04-23 17:05:33 +02:00
|
|
|
logger.debug('Complete', logger.ts(ts));
|
|
|
|
return callback && callback();
|
2020-05-21 21:36:26 +02:00
|
|
|
}, windowId);
|
2016-04-08 17:40:00 +02:00
|
|
|
});
|
|
|
|
} catch (ex) {
|
2016-08-18 22:01:08 +02:00
|
|
|
this.running = false;
|
2016-04-08 17:40:00 +02:00
|
|
|
logger.error('Parse error', ex);
|
2016-04-23 17:05:33 +02:00
|
|
|
return callback && callback(ex);
|
2016-04-08 17:40:00 +02:00
|
|
|
}
|
2016-04-09 11:17:01 +02:00
|
|
|
},
|
|
|
|
|
2016-08-07 19:34:31 +02:00
|
|
|
validate(entry, sequence, callback) {
|
2016-04-23 16:50:40 +02:00
|
|
|
try {
|
2017-01-31 07:50:28 +01:00
|
|
|
const parser = new AutoTypeParser(sequence);
|
|
|
|
const runner = parser.parse();
|
2020-05-09 10:37:34 +02:00
|
|
|
runner.resolve(entry, null, callback);
|
2016-04-23 16:50:40 +02:00
|
|
|
} catch (ex) {
|
|
|
|
return callback(ex);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-08-07 19:34:31 +02:00
|
|
|
printOps(ops) {
|
2016-04-09 11:17:01 +02:00
|
|
|
return '[' + ops.map(this.printOp, this).join(',') + ']';
|
|
|
|
},
|
|
|
|
|
2016-08-07 19:34:31 +02:00
|
|
|
printOp(op) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const mod = op.mod ? Object.keys(op.mod).join('') : '';
|
2016-04-09 11:17:01 +02:00
|
|
|
if (op.type === 'group') {
|
|
|
|
return mod + this.printOps(op.value);
|
|
|
|
}
|
|
|
|
if (op.type === 'text') {
|
2017-01-31 07:50:28 +01:00
|
|
|
let value = op.value;
|
2016-04-09 11:28:14 +02:00
|
|
|
if (!clearTextAutoTypeLog) {
|
|
|
|
value = value.replace(/./g, '*');
|
|
|
|
}
|
|
|
|
return mod + value;
|
2016-04-09 11:17:01 +02:00
|
|
|
}
|
|
|
|
return mod + op.type + ':' + op.value;
|
2016-04-09 20:58:22 +02:00
|
|
|
},
|
|
|
|
|
2016-08-07 19:34:31 +02:00
|
|
|
hideWindow(callback) {
|
2016-04-09 20:58:22 +02:00
|
|
|
logger.debug('Hide window');
|
2016-07-24 19:11:25 +02:00
|
|
|
if (Launcher.isAppFocused()) {
|
|
|
|
Launcher.hideApp();
|
2016-04-09 20:58:22 +02:00
|
|
|
setTimeout(callback, Timeouts.AutoTypeAfterHide);
|
|
|
|
} else {
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-09-12 22:38:21 +02:00
|
|
|
getActiveWindowInfo(callback) {
|
2021-03-08 22:29:59 +01:00
|
|
|
logger.debug('Getting window info');
|
|
|
|
const helper = new AutoTypeHelper();
|
2021-01-30 14:55:00 +01:00
|
|
|
return helper.getActiveWindowInfo((err, windowInfo) => {
|
2016-04-09 20:58:22 +02:00
|
|
|
if (err) {
|
2019-09-12 22:38:21 +02:00
|
|
|
logger.error('Error getting window info', err);
|
2016-04-09 20:58:22 +02:00
|
|
|
} else {
|
2019-09-12 22:38:21 +02:00
|
|
|
if (!windowInfo.url) {
|
2019-01-03 08:43:22 +01:00
|
|
|
// try to find a URL in the title
|
2020-11-19 18:43:50 +01:00
|
|
|
const urlMatcher = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\\+.~#?&\/=]*)/;
|
2019-09-12 22:38:21 +02:00
|
|
|
const urlMatches = urlMatcher.exec(windowInfo.title);
|
|
|
|
windowInfo.url = urlMatches && urlMatches.length > 0 ? urlMatches[0] : null;
|
2019-01-03 08:43:22 +01:00
|
|
|
}
|
2019-09-12 22:38:21 +02:00
|
|
|
logger.debug('Window info', windowInfo.id, windowInfo.title, windowInfo.url);
|
2016-04-09 20:58:22 +02:00
|
|
|
}
|
2019-09-12 22:38:21 +02:00
|
|
|
return callback(err, windowInfo);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
activeWindowMatches(windowInfo, callback) {
|
|
|
|
if (!windowInfo || !windowInfo.id) {
|
|
|
|
logger.debug('Skipped active window check because window id is unknown');
|
|
|
|
return callback(true);
|
|
|
|
}
|
|
|
|
this.getActiveWindowInfo((err, activeWindowInfo) => {
|
|
|
|
if (!activeWindowInfo) {
|
|
|
|
logger.debug('Error during active window check, something is wrong', err);
|
|
|
|
return callback(false);
|
|
|
|
}
|
2020-05-22 12:30:01 +02:00
|
|
|
if (activeWindowInfo.id !== windowInfo.id && !this.supportsEventsWithWindowId) {
|
2019-09-12 22:38:21 +02:00
|
|
|
logger.info(
|
|
|
|
`Active window doesn't match: ID is different. ` +
|
|
|
|
`Expected ${windowInfo.id}, got ${activeWindowInfo.id}`
|
|
|
|
);
|
|
|
|
return callback(false, activeWindowInfo);
|
|
|
|
}
|
2020-05-22 21:05:30 +02:00
|
|
|
if (activeWindowInfo.url !== windowInfo.url && !this.supportsEventsWithWindowId) {
|
2019-09-12 22:38:21 +02:00
|
|
|
logger.info(
|
|
|
|
`Active window doesn't match: url is different. ` +
|
|
|
|
`Expected "${windowInfo.url}", got "${activeWindowInfo.url}"`
|
|
|
|
);
|
|
|
|
return callback(false, activeWindowInfo);
|
|
|
|
}
|
|
|
|
logger.info('Active window matches');
|
|
|
|
callback(true, activeWindowInfo);
|
2016-07-24 19:11:25 +02:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-08-07 19:34:31 +02:00
|
|
|
selectEntryAndRun() {
|
2021-04-26 11:20:20 +02:00
|
|
|
this.getActiveWindowInfo(async (e, windowInfo) => {
|
2021-04-28 11:29:48 +02:00
|
|
|
const filter = new SelectEntryFilter(
|
|
|
|
windowInfo,
|
|
|
|
AppModel.instance,
|
|
|
|
AppModel.instance.files,
|
|
|
|
{
|
|
|
|
autoType: true
|
|
|
|
}
|
|
|
|
);
|
2019-09-12 22:38:21 +02:00
|
|
|
const evt = { filter, windowInfo };
|
2020-05-05 16:34:39 +02:00
|
|
|
if (!AppModel.instance.files.hasOpenFiles()) {
|
2016-08-07 19:34:31 +02:00
|
|
|
logger.debug('auto-type event delayed');
|
2016-08-18 23:26:35 +02:00
|
|
|
this.focusMainWindow();
|
2021-04-26 11:20:20 +02:00
|
|
|
try {
|
2021-04-26 11:51:24 +02:00
|
|
|
await AppModel.instance.unlockAnyFile('autoTypeUnlockMessage');
|
2021-04-26 11:20:20 +02:00
|
|
|
} catch {
|
|
|
|
logger.debug('auto-type event canceled');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this.selectEntryView) {
|
|
|
|
this.selectEntryView.show();
|
|
|
|
}
|
2016-07-24 19:11:25 +02:00
|
|
|
}
|
2021-04-26 11:20:20 +02:00
|
|
|
logger.debug('processing auto-type event');
|
|
|
|
this.processEventWithFilter(evt);
|
2016-08-07 19:34:31 +02:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-08-18 23:26:35 +02:00
|
|
|
focusMainWindow() {
|
2021-04-28 08:23:15 +02:00
|
|
|
if (!Launcher.isAppFocused()) {
|
|
|
|
setTimeout(() => Launcher.showMainWindow(), Timeouts.RedrawInactiveWindow);
|
|
|
|
}
|
2016-08-18 23:26:35 +02:00
|
|
|
},
|
|
|
|
|
2016-08-07 19:34:31 +02:00
|
|
|
processEventWithFilter(evt) {
|
2021-05-09 14:07:22 +02:00
|
|
|
let entries = evt.filter.getEntries();
|
2019-09-17 19:50:42 +02:00
|
|
|
if (entries.length === 1 && AppSettingsModel.directAutotype) {
|
2016-08-24 18:19:59 +02:00
|
|
|
this.hideWindow(() => {
|
2020-05-21 21:36:26 +02:00
|
|
|
this.runAndHandleResult({ entry: entries[0] }, evt.windowInfo.id);
|
2016-08-24 18:19:59 +02:00
|
|
|
});
|
2016-08-07 19:34:31 +02:00
|
|
|
return;
|
|
|
|
}
|
2016-08-18 23:26:35 +02:00
|
|
|
this.focusMainWindow();
|
2021-04-28 11:29:48 +02:00
|
|
|
|
2021-05-09 14:07:22 +02:00
|
|
|
if (entries.length === 0) {
|
|
|
|
if (evt.filter.useUrl) {
|
|
|
|
evt.filter.useUrl = false;
|
2021-05-12 21:00:51 +02:00
|
|
|
if (evt.filter.title && AppSettingsModel.autoTypeTitleFilterEnabled) {
|
2021-05-09 14:07:22 +02:00
|
|
|
evt.filter.useTitle = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
entries = evt.filter.getEntries();
|
|
|
|
if (entries.length === 0 && evt.filter.useTitle) {
|
|
|
|
evt.filter.useTitle = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-28 11:29:48 +02:00
|
|
|
const humanReadableTarget = evt.filter.title || evt.filter.url;
|
|
|
|
const topMessage = humanReadableTarget
|
|
|
|
? Locale.autoTypeMsgMatchedByWindow.replace('{}', humanReadableTarget)
|
|
|
|
: Locale.autoTypeMsgNoWindow;
|
|
|
|
|
|
|
|
this.selectEntryView = new SelectEntryView({
|
|
|
|
isAutoType: true,
|
|
|
|
itemOptions: true,
|
|
|
|
filter: evt.filter,
|
|
|
|
topMessage
|
|
|
|
});
|
2020-06-01 16:53:51 +02:00
|
|
|
this.selectEntryView.on('result', (result) => {
|
2016-08-07 19:34:31 +02:00
|
|
|
logger.debug('Entry selected', result);
|
|
|
|
this.selectEntryView.off('result');
|
|
|
|
this.selectEntryView.remove();
|
|
|
|
this.selectEntryView = null;
|
|
|
|
this.hideWindow(() => {
|
2020-11-19 13:00:56 +01:00
|
|
|
if (result?.entry) {
|
2019-09-12 22:38:21 +02:00
|
|
|
this.activeWindowMatches(evt.windowInfo, (matches, activeWindowInfo) => {
|
|
|
|
if (matches) {
|
2020-05-21 21:36:26 +02:00
|
|
|
this.runAndHandleResult(result, evt.windowInfo.id);
|
2019-09-12 22:38:21 +02:00
|
|
|
}
|
|
|
|
});
|
2016-07-24 22:57:12 +02:00
|
|
|
}
|
2016-07-24 19:11:25 +02:00
|
|
|
});
|
2016-04-09 20:58:22 +02:00
|
|
|
});
|
2019-09-15 23:02:51 +02:00
|
|
|
this.selectEntryView.render();
|
2021-04-28 08:23:15 +02:00
|
|
|
this.selectEntryView.on('show-open-files', async () => {
|
2019-03-31 14:30:14 +02:00
|
|
|
this.selectEntryView.hide();
|
2021-04-28 08:23:15 +02:00
|
|
|
try {
|
|
|
|
await AppModel.instance.unlockAnyFile('autoTypeUnlockMessage');
|
|
|
|
} catch {
|
2021-04-28 11:29:48 +02:00
|
|
|
this.selectEntryView.emit('result', undefined);
|
2021-04-28 08:23:15 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.selectEntryView.show();
|
|
|
|
this.selectEntryView.render();
|
2019-03-31 14:30:14 +02:00
|
|
|
});
|
2016-04-08 17:40:00 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-09-15 14:16:32 +02:00
|
|
|
export { AutoType };
|