Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebase against the upstream Projector sources #99

Merged
merged 4 commits into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions projector-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
*/
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin

plugins {
kotlin("multiplatform") apply false
Expand Down Expand Up @@ -61,6 +63,10 @@ subprojects {
}
}

plugins.withType(NodeJsRootPlugin::class.java) {
the<NodeJsRootExtension>().nodeVersion = "16.14.2"
}

if (System.getenv("CHROME_BIN") == null) {
gradle.taskGraph.beforeTask {
if (name in setOf("jsTest", "jsBrowserTest", "browserTest")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Unfortunately, we can't just continuously get clipboard data from [`window.navig

It's vice versa: when your clipboard is changed on the server side, the client needs to apply the change on its side.

We set the clipboard on the client side via [`window.navigator.clipboard`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard). **This doesn't work in [insecure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts), so the client needs to be opened using HTTPS or on localhost to support this**.
We set the clipboard on the client side via [`window.navigator.clipboard`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard) when client is opened using HTTPS or on localhost. If client is opened in insecure context or doesn't support Async Clipboard API we fall back to `document.execCommand("copy")`. If it also didn't work out then we show prompt so that you can manually copy new contents of server clipboard.

We can't use ["copy" listener](https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_event) because when this event is generated, we don't have a message from the server with actual clipboard data yet. Also, this method won't work if you click a "copy" button in your application.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ Name | Type | Default value

Setting this option to `false` will enable the platform and plugins updates.

### Disabling attaching to the IDE

Name | Type | Default value
---|---|---
`ORG_JETBRAINS_PROJECTOR_SERVER_ATTACH_TO_IDE` | Boolean | `true`

Setting this option to `false` will disable integrations of Projector with the IDE. By default (`true`), Projector decides it automatically: it attaches if it's able to do so.

## Difference between env vars and system props

Expand Down
2 changes: 1 addition & 1 deletion projector-client/docSrc/src/doc/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extra:
- icon: fontawesome/brands/github
# TODO: rename to "projector"?
link: https://github.com/JetBrains/projector-client
- icon: fontawesome/brands/telegram-plane
- icon: fontawesome/brands/telegram
link: https://t.me/JBProjector
- icon: fontawesome/brands/twitter
link: https://twitter.com/ProjectorJB
Expand Down
2 changes: 1 addition & 1 deletion projector-client/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ coroutinesVersion=1.6.0
dnsjavaVersion=3.4.3
electronVersion=^16.0.6
electronPackagerVersion=^15.1.0
gradleMkdocsPluginVersion=2.2.0
gradleMkdocsPluginVersion=2.3.0
intellijPlatformVersion=213.6461.23
intellijMarkdownPluginVersion=213.5744.223
intellijJcefVersion=89.0.12-g2b76680-chromium-89.0.4389.90-api-1.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ tasks.withType<Jar> {
)
}

duplicatesStrategy = DuplicatesStrategy.WARN

exclude("META-INF/versions/9/module-info.class")

from(inline(configurations.runtimeClasspath))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,10 @@ class Renderer(private val renderingSurface: RenderingSurface) {
requestedState.paint = color.toColor()
}

fun setGradientPaint(p1: Point, p2: Point, color1: Int, color2: Int) {
val linearGradient = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y).apply {
addColorStop(0.0, color1)
addColorStop(1.0, color2)
fun setGradientPaint(p1: Point, p2: Point, fractions: List<Double>, argbs: List<Int>) {
val linearGradient = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y)
fractions.zip(argbs).forEach { (fraction, argb) ->
linearGradient.addColorStop(fraction, argb)
}

requestedState.paint = linearGradient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ class SingleRenderingSurfaceProcessor(private val renderingSurface: RenderingSur
is PaintValue.Gradient -> renderer.setGradientPaint(
p1 = paintValue.p1,
p2 = paintValue.p2,
color1 = paintValue.argb1,
color2 = paintValue.argb2
fractions = paintValue.fractions,
argbs = paintValue.argbs,
)

is PaintValue.Unknown -> logUnsupportedCommand(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ actual object ParamsProvider {
private const val DEFAULT_REPAINT_INTERVAL_MS = 333
private const val DEFAULT_IMAGE_CACHE_SIZE_CHARS = 5_000_000
private const val DEFAULT_BLOCK_CLOSING = true
private const val DEFAULT_SPECULATIVE_TYPING_LATENCY = 0

val SYSTEM_SCALING_RATIO
get() = window.devicePixelRatio // get every time because it can be changed
Expand Down Expand Up @@ -101,6 +102,7 @@ actual object ParamsProvider {
actual val IMAGE_CACHE_SIZE_CHARS: Int
val BLOCK_CLOSING: Boolean
val LAYOUT_TYPE: LayoutType
val SPECULATIVE_TYPING_LATENCY: Int
val SCALING_RATIO: Double
get() = SYSTEM_SCALING_RATIO * USER_SCALING_RATIO

Expand Down Expand Up @@ -162,6 +164,7 @@ actual object ParamsProvider {
"frAzerty" -> LayoutType.FR_AZERTY
else -> LayoutType.JS_DEFAULT
}
SPECULATIVE_TYPING_LATENCY = searchParams.get("speculativeTypingLatency")?.toIntOrNull() ?: DEFAULT_SPECULATIVE_TYPING_LATENCY
}
}

Expand Down
1 change: 1 addition & 0 deletions projector-client/projector-client-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Name | Type | Default value | Description
`cacheSize` | Int | `5M` | Set size of cache for images in Chars.
`blockClosing` | Boolean | `true` | Enable blocking of accidental closing of the web page
`relayServerId` | String? | Not present | Identifier of Projector server to connect to for relay connection. Warning: Static files must be accessed via https when relay is used.
`speculativeTypingLatency` | Int | `0` | Sets latency before key press event is sent to server if speculative symbol for the event was drawn.

## Shortcuts
- `Ctrl + F10` prints statistics to the browser console. Example:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 JetBrains s.r.o.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.jetbrains.projector.client.web

import kotlinx.browser.document
import kotlinx.browser.window
import org.jetbrains.projector.client.web.misc.isDefined
import org.jetbrains.projector.client.web.misc.isElectron
import org.jetbrains.projector.client.web.misc.isGecko
import org.jetbrains.projector.common.protocol.toServer.ClientNotificationEvent
import org.jetbrains.projector.common.protocol.toServer.ClientNotificationType
import org.jetbrains.projector.util.logging.Logger
import org.w3c.dom.HTMLTextAreaElement

/**
* First try to copy via `window.navigator.clipboard`. If this method fails or is not available,
* then try to copy via `document.execCommand` (deprecated, but still supported by firefox).
* If this also fails, then fallback to asking user to copy manually.
*/
class ClipboardHandler(private val onCopyFailed: (ClientNotificationEvent) -> Unit) {

private val logger = Logger<ClipboardHandler>()

fun copyText(text: String) {
if (isDefined(window.navigator.clipboard)) {
window.navigator.clipboard.writeText(text)
.catch {
logger.error(it) { "Error writing clipboard: $it" }
fallbackCopy(text, it.message ?: "Unknown error")
}
}
else {
fallbackCopy(text, "Clipboard API is not available")
}
}

private fun fallbackCopy(textToCopy: String, reason: String) {
val execCommandReason = tryCopyTextViaExecCommand(textToCopy)
if (execCommandReason != null) {
askUserToCopyClipboardManually(textToCopy, reason, execCommandReason)
}
}

/**
* Tries to automatically copy text to clipboard via Document.execCommand
*
* @return null if copy successful, string describing fail reason otherwise
*/
private fun tryCopyTextViaExecCommand(textToCopy: String): String? {

when {
!isDefined(js("document.execCommand")) -> return "Document.execCommand is not available"

// Chrome only allows Document.execCommand from event callbacks
!isGecko() -> return "Copying using Document.execCommand works only in Firefox"
}

val textArea = document.createElement("textarea") as HTMLTextAreaElement
textArea.id = "copyArea"
textArea.value = textToCopy
textArea.style.display = "hidden"

document.body!!.appendChild(textArea)

textArea.focus()
textArea.select()

var isCopied = false
try {
isCopied = document.execCommand("copy")
}
catch (t: Throwable) { // May throw SecurityError https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#the-copy-command
logger.error(t) { "Error while running 'document.execCommand(\"copy\")'" }
}
finally {
textArea.remove()
}

return if (isCopied) null else "Document.execCommand failed, check console for info from browser"
}

private fun askUserToCopyClipboardManually(textToCopy: String, vararg reasons: String) {
val message = "A clipboard change on the server detected but can't synchronize your clipboard with it automatically " +
"(reasons: ${reasons.joinToString("; ")}). Please copy text on next line manually:"

if (isElectron()) { // TODO window.prompt is not available in electron
val title = "Copying failed"
val notificationMessage = "Copying is not currently supported in insecure context in Projector launcher"
val type = ClientNotificationType.ERROR
onCopyFailed(ClientNotificationEvent(title, notificationMessage, type))
}
else {
window.prompt(message, textToCopy)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,29 @@
*/
package org.jetbrains.projector.client.web

import kotlinx.browser.window
import org.jetbrains.projector.client.common.RenderingQueue
import org.jetbrains.projector.client.web.component.MarkdownPanelManager
import org.jetbrains.projector.client.web.input.InputController
import org.jetbrains.projector.client.web.misc.PingStatistics
import org.jetbrains.projector.client.web.misc.*
import org.jetbrains.projector.client.web.speculative.Typing
import org.jetbrains.projector.client.web.state.ClientAction
import org.jetbrains.projector.client.web.state.ClientStateMachine
import org.jetbrains.projector.client.web.state.ProjectorUI
import org.jetbrains.projector.client.web.window.OnScreenMessenger
import org.jetbrains.projector.client.web.window.WindowDataEventsProcessor
import org.jetbrains.projector.client.web.window.WebWindowManager
import org.jetbrains.projector.client.web.window.WindowDataEventsProcessor
import org.jetbrains.projector.common.misc.Do
import org.jetbrains.projector.common.protocol.toClient.*
import org.jetbrains.projector.util.logging.Logger

class ServerEventsProcessor(
private val windowManager: WebWindowManager,
private val windowDataEventsProcessor: WindowDataEventsProcessor,
private val renderingQueue: RenderingQueue,
private val stateMachine: ClientStateMachine,
) {

private val clipboardHandler = ClipboardHandler { stateMachine.fire(ClientAction.AddEvent(it)) }

fun process(
commands: ToClientMessageType, pingStatistics: PingStatistics, typing: Typing, markdownPanelManager: MarkdownPanelManager,
inputController: InputController,
Expand All @@ -68,7 +71,7 @@ class ServerEventsProcessor(
inputController.handleCaretInfoChange(command.data)
}

is ServerClipboardEvent -> handleServerClipboardChange(command)
is ServerClipboardEvent -> clipboardHandler.copyText(command.stringContent)

is ServerPingReplyEvent -> pingStatistics.onPingReply(command)

Expand Down Expand Up @@ -111,13 +114,4 @@ class ServerEventsProcessor(
windowDataEventsProcessor.onResized()
}

private fun handleServerClipboardChange(event: ServerClipboardEvent) {
window.navigator.clipboard.writeText(event.stringContent)
.catch { logger.error { "Error writing clipboard: $it" } }
}

companion object {

private val logger = Logger<ServerEventsProcessor>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
package org.jetbrains.projector.client.web

import kotlinx.browser.window
import org.jetbrains.projector.client.web.electron.isElectron
import org.jetbrains.projector.client.web.misc.isElectron
import org.w3c.dom.url.URL

object UriHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,10 @@ class WindowSizeController(private val stateMachine: ClientStateMachine) {
stateMachine.fire(ClientAction.WindowResize)
}

fun addListener() {
init {
window.addEventListener(RESIZE_EVENT_TYPE, ::handleResizeEvent)
}

fun removeListener() {
window.removeEventListener(RESIZE_EVENT_TYPE, ::handleResizeEvent)
}

companion object {

private const val RESIZE_EVENT_TYPE = "resize"
Expand Down
Loading