Skip to content

Commit

Permalink
Clipboard Plugin API
Browse files Browse the repository at this point in the history
Signed-off-by: Artem Zatsarynnyi <azatsary@redhat.com>
  • Loading branch information
azatsarynnyy committed Sep 17, 2019
1 parent 1808d78 commit 7ab689f
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 5 deletions.
111 changes: 111 additions & 0 deletions packages/core/src/browser/browser-clipboard-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject } from 'inversify';
import { isFirefox } from './browser';
import { ClipboardService } from './clipboard-service';
import { ILogger } from '../common/logger';
import { MessageService } from '../common/message-service';

export interface NavigatorClipboard {
readText(): Promise<string>;
writeText(value: string): Promise<void>;
}
export interface PermissionStatus {
state: 'granted' | 'prompt' | 'denied'
}
export interface NavigatorPermissions {
query(options: { name: string }): Promise<PermissionStatus>
}

@injectable()
export class BrowserClipboardService implements ClipboardService {

@inject(MessageService)
protected readonly messageService: MessageService;

@inject(ILogger)
protected readonly logger: ILogger;

async readText(): Promise<string> {
let permission;
try {
permission = await this.queryPermission('clipboard-read');
} catch (e) {
this.logger.error('Failed checking a clipboard-read permission.', e);
// in FireFox, Clipboard API isn't gated with the permissions
try {
return await this.getClipboardAPI().readText();
} catch (e) {
this.logger.error('Failed reading clipboard content.', e);
if (isFirefox) {
this.messageService.warn(`Clipboard API is not available.
It can be enabled by 'dom.events.testing.asyncClipboard' preference on 'about:config' page. Then reload Theia.
Note, it will allow FireFox getting full access to the system clipboard.`);
}
throw new Error('Failed reading clipboard content.');
}
}
if (permission.state === 'denied') {
// most likely, the user intentionally denied the access
this.messageService.error("Access to the clipboard is denied. Check your browser's permission.");
throw new Error('Access to the clipboard is denied.');
}
return this.getClipboardAPI().readText();
}

async writeText(value: string): Promise<void> {
let permission;
try {
permission = await this.queryPermission('clipboard-write');
} catch (e) {
this.logger.error('Failed checking a clipboard-write permission.', e);
// in FireFox, Clipboard API isn't gated with the permissions
try {
await this.getClipboardAPI().writeText(value);
return;
} catch (e) {
this.logger.error('Failed writing to the clipboard.', e);
if (isFirefox) {
this.messageService.warn(`Clipboard API is not available.
It can be enabled by 'dom.events.testing.asyncClipboard' preference on 'about:config' page. Then reload Theia.
Note, it will allow FireFox getting full access to the system clipboard.`);
}
throw new Error('Failed writing the the clipboard.');
}
}
if (permission.state === 'denied') {
// most likely, the user intentionally denied the access
this.messageService.error("Access to the clipboard is denied. Check your browser's permission.");
throw new Error('Access to the clipboard is denied.');
}
return this.getClipboardAPI().writeText(value);
}

protected async queryPermission(name: string): Promise<PermissionStatus> {
if ('permissions' in navigator) {
return (<NavigatorPermissions>navigator['permissions']).query({ name: name });
}
throw new Error('Permissions API unavailable');
}

protected getClipboardAPI(): NavigatorClipboard {
if ('clipboard' in navigator) {
return (<NavigatorClipboard>navigator['clipboard']);
}
throw new Error('Async Clipboard API unavailable');
}
}
23 changes: 23 additions & 0 deletions packages/core/src/browser/clipboard-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { MaybePromise } from '../common/types';

export const ClipboardService = Symbol('ClipboardService');
export interface ClipboardService {
readText(): MaybePromise<string>;
writeText(value: string): MaybePromise<void>;
}
3 changes: 3 additions & 0 deletions packages/core/src/browser/window/browser-window-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import { ContainerModule } from 'inversify';
import { WindowService } from '../../browser/window/window-service';
import { DefaultWindowService } from '../../browser/window/default-window-service';
import { FrontendApplicationContribution } from '../frontend-application';
import { ClipboardService } from '../clipboard-service';
import { BrowserClipboardService } from '../browser-clipboard-service';

export default new ContainerModule(bind => {
bind(DefaultWindowService).toSelf().inSingletonScope();
bind(WindowService).toService(DefaultWindowService);
bind(FrontendApplicationContribution).toService(DefaultWindowService);
bind(ClipboardService).to(BrowserClipboardService).inSingletonScope();
});
32 changes: 32 additions & 0 deletions packages/core/src/electron-browser/electron-clipboard-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { clipboard } from 'electron';
import { injectable } from 'inversify';
import { ClipboardService } from '../browser/clipboard-service';

@injectable()
export class ElectronClipboardService implements ClipboardService {

readText(): string {
return clipboard.readText();
}

writeText(value: string): void {
clipboard.writeText(value);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import { ContainerModule } from 'inversify';
import { WindowService } from '../../browser/window/window-service';
import { ElectronWindowService } from './electron-window-service';
import { FrontendApplicationContribution } from '../../browser/frontend-application';
import { ElectronClipboardService } from '../electron-clipboard-service';
import { ClipboardService } from '../../browser/clipboard-service';

export default new ContainerModule(bind => {
bind(WindowService).to(ElectronWindowService).inSingletonScope();
bind(FrontendApplicationContribution).toService(WindowService);
bind(ClipboardService).to(ElectronClipboardService).inSingletonScope();
});
8 changes: 7 additions & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,11 @@ export interface FileSystemMain {
$unregisterProvider(handle: number): void;
}

export interface ClipboardMain {
$readText(): Promise<string>;
$writeText(value: string): Promise<void>;
}

export const PLUGIN_RPC_CONTEXT = {
COMMAND_REGISTRY_MAIN: <ProxyIdentifier<CommandRegistryMain>>createProxyIdentifier<CommandRegistryMain>('CommandRegistryMain'),
QUICK_OPEN_MAIN: createProxyIdentifier<QuickOpenMain>('QuickOpenMain'),
Expand All @@ -1280,7 +1285,8 @@ export const PLUGIN_RPC_CONTEXT = {
FILE_SYSTEM_MAIN: createProxyIdentifier<FileSystemMain>('FileSystemMain'),
SCM_MAIN: createProxyIdentifier<ScmMain>('ScmMain'),
DECORATIONS_MAIN: createProxyIdentifier<DecorationsMain>('DecorationsMain'),
WINDOW_MAIN: createProxyIdentifier<WindowMain>('WindowMain')
WINDOW_MAIN: createProxyIdentifier<WindowMain>('WindowMain'),
CLIPBOARD_MAIN: <ProxyIdentifier<ClipboardMain>>createProxyIdentifier<ClipboardMain>('ClipboardMain')
};

export const MAIN_RPC_CONTEXT = {
Expand Down
5 changes: 4 additions & 1 deletion packages/plugin-ext/src/hosted/browser/worker/worker-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { EditorsAndDocumentsExtImpl } from '../../../plugin/editors-and-document
import { WorkspaceExtImpl } from '../../../plugin/workspace';
import { MessageRegistryExt } from '../../../plugin/message-registry';
import { WorkerEnvExtImpl } from './worker-env-ext';
import { ClipboardExt } from '../../../plugin/clipboard-ext';

// tslint:disable-next-line:no-any
const ctx = self as any;
Expand Down Expand Up @@ -55,6 +56,7 @@ const messageRegistryExt = new MessageRegistryExt(rpc);
const workspaceExt = new WorkspaceExtImpl(rpc, editorsAndDocuments, messageRegistryExt);
const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt);
const debugExt = createDebugExtStub(rpc);
const clipboardExt = new ClipboardExt(rpc);

const pluginManager = new PluginManagerExtImpl({
// tslint:disable-next-line:no-any
Expand Down Expand Up @@ -133,7 +135,8 @@ const apiFactory = createAPIFactory(
preferenceRegistryExt,
editorsAndDocuments,
workspaceExt,
messageRegistryExt
messageRegistryExt,
clipboardExt
);
let defaultApi: typeof theia;

Expand Down
5 changes: 4 additions & 1 deletion packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { EditorsAndDocumentsExtImpl } from '../../plugin/editors-and-documents';
import { WorkspaceExtImpl } from '../../plugin/workspace';
import { MessageRegistryExt } from '../../plugin/message-registry';
import { EnvNodeExtImpl } from '../../plugin/node/env-node-ext';
import { ClipboardExt } from '../../plugin/clipboard-ext';

/**
* Handle the RPC calls.
Expand All @@ -47,6 +48,7 @@ export class PluginHostRPC {
const messageRegistryExt = new MessageRegistryExt(this.rpc);
const workspaceExt = new WorkspaceExtImpl(this.rpc, editorsAndDocumentsExt, messageRegistryExt);
const preferenceRegistryExt = new PreferenceRegistryExtImpl(this.rpc, workspaceExt);
const clipboardExt = new ClipboardExt(this.rpc);
this.pluginManager = this.createPluginManager(envExt, preferenceRegistryExt, this.rpc);
this.rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, this.pluginManager);
this.rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, editorsAndDocumentsExt);
Expand All @@ -61,7 +63,8 @@ export class PluginHostRPC {
preferenceRegistryExt,
editorsAndDocumentsExt,
workspaceExt,
messageRegistryExt
messageRegistryExt,
clipboardExt
);
}

Expand Down
38 changes: 38 additions & 0 deletions packages/plugin-ext/src/main/browser/clipboard-main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { interfaces } from 'inversify';
import { ClipboardMain } from '../../common';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';

export class ClipboardMainImpl implements ClipboardMain {

protected readonly clipboardService: ClipboardService;

constructor(container: interfaces.Container) {
this.clipboardService = container.get(ClipboardService);
}

async $readText(): Promise<string> {
const result = await this.clipboardService.readText();
return result;
}

async $writeText(value: string): Promise<void> {
await this.clipboardService.writeText(value);
}

}
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/main/browser/main-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { DebugMainImpl } from './debug/debug-main';
import { FileSystemMainImpl } from './file-system-main';
import { ScmMainImpl } from './scm-main';
import { DecorationsMainImpl } from './decorations/decorations-main';
import { ClipboardMainImpl } from './clipboard-main';

export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void {
const commandRegistryMain = new CommandRegistryMainImpl(rpc, container);
Expand Down Expand Up @@ -113,4 +114,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container

const windowMain = new WindowStateMain(rpc, container);
rpc.set(PLUGIN_RPC_CONTEXT.WINDOW_MAIN, windowMain);

const clipboardMain = new ClipboardMainImpl(container);
rpc.set(PLUGIN_RPC_CONTEXT.CLIPBOARD_MAIN, clipboardMain);
}
37 changes: 37 additions & 0 deletions packages/plugin-ext/src/plugin/clipboard-ext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import * as theia from '@theia/plugin';
import { RPCProtocol } from '../common/rpc-protocol';
import { PLUGIN_RPC_CONTEXT, ClipboardMain } from '../common';

export class ClipboardExt implements theia.Clipboard {

protected readonly proxy: ClipboardMain;

constructor(rpc: RPCProtocol) {
this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.CLIPBOARD_MAIN);
}

readText(): Promise<string> {
return this.proxy.$readText();
}

writeText(value: string): Promise<void> {
return this.proxy.$writeText(value);
}

}
6 changes: 4 additions & 2 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ import { ScmExtImpl } from './scm';
import { DecorationProvider, LineChange } from '@theia/plugin';
import { DecorationsExtImpl } from './decorations';
import { TextEditorExt } from './text-editor';
import { ClipboardExt } from './clipboard-ext';

export function createAPIFactory(
rpc: RPCProtocol,
Expand All @@ -148,7 +149,8 @@ export function createAPIFactory(
preferenceRegistryExt: PreferenceRegistryExtImpl,
editorsAndDocumentsExt: EditorsAndDocumentsExtImpl,
workspaceExt: WorkspaceExtImpl,
messageRegistryExt: MessageRegistryExt
messageRegistryExt: MessageRegistryExt,
clipboard: ClipboardExt
): PluginAPIFactory {

const commandRegistry = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc));
Expand Down Expand Up @@ -491,7 +493,7 @@ export function createAPIFactory(
get machineId(): string { return envExt.machineId; },
get sessionId(): string { return envExt.sessionId; },
get uriScheme(): string { return envExt.uriScheme; },

clipboard,
getEnvVariable(envVarName: string): PromiseLike<string | undefined> {
return envExt.getEnvVariable(envVarName);
},
Expand Down
Loading

0 comments on commit 7ab689f

Please sign in to comment.