diff --git a/src/ui/ab.js b/src/ui/ab.js new file mode 100644 index 000000000..c72d475df --- /dev/null +++ b/src/ui/ab.js @@ -0,0 +1,283 @@ +import { handler, string2RGB, platformSvg, msgbox,translate,is_win,OS } from "./common.js"; +import { app, formatId, createNewConnect,svg_menu } from "./index.js"; // TODO check app obj +// TODO transform +const svg_tile = ; +const svg_list = ; +const search_icon = ; +const clear_icon = ; + +function getSessionsStyleOption(type) { + return (type || "recent") + "-sessions-style"; +} + +export function getSessionsStyle(type) { + var v = handler.xcall("get_local_option",getSessionsStyleOption(type)); + if (!v) v = type == "ab" ? "list" : "tile"; + return v; +} + +var searchPatterns = {}; + +export class SearchBar extends Element { + + this(props) { + this.type = (props || {}).type || ""; + } + + render() { + + let value = searchPatterns[this.type] || ""; + setTimeout(() => { this.$("input").value = value; }, 1); + return (
+ {search_icon} + + {value && {clear_icon}} +
); + } + + ["on click at span.clear-input"](_) { + this.$("input").value = ''; + this.onChange(''); + } + + ["on change at input"](_, el) { + this.onChange(el.value.trim()); + } + + onChange(v) { + searchPatterns[this.type] = v; + app.multipleSessions.update(); + } +} + +export class SessionStyle extends Element { + type = ""; + + this(props) { + this.type = (props || {}).type || ""; + } + + render() { + let sessionsStyle = getSessionsStyle(this.type); + return (
+ {svg_tile} + {svg_list} +
); + } + + ["on click at span.inactive"](_) { + let option = getSessionsStyleOption(this.type); + let sessionsStyle = getSessionsStyle(this.type); + handler.xcall("set_option", option, sessionsStyle == "tile" ? "list" : "tile"); + app.componentUpdate(); + } +} + +export class SessionList extends Element { + sessions = []; + type = ""; + style; + + + this(props) { + this.sessions = props.sessions; + this.type = props.type; + this.style = getSessionsStyle(props.type); + } + + getSessions() { + let p = searchPatterns[this.type]; + if (!p) return this.sessions; + let tmp = []; + this.sessions.map( (s) => { + let name = s[4] || s.alias || s[0] || s.id || ""; + if (name.indexOf(p) >= 0) tmp.push(s); + }); + return tmp; + } + + render() { + let sessions = this.getSessions(); + if (sessions.length == 0) return
{translate("Empty")}
; + sessions = sessions.map((x) => this.getSession(x)); + return
+ + +
  • {translate('Connect')}
  • +
  • {translate('Transfer File')}
  • +
  • {translate('TCP Tunneling')}
  • +
  • RDP
  • +
    +
  • {translate('Rename')}
  • + {this.type != "fav" &&
  • {translate('Remove')}
  • } + {is_win &&
  • {translate('Create Desktop Shortcut')}
  • } +
  • {translate('Unremember Password')}
  • + {(!this.type || this.type == "fav") &&
  • {translate('Add to Favorites')}
  • } + {(!this.type || this.type == "fav") &&
  • {translate('Remove from Favorites')}
  • } +
    +
    + {sessions} +
    ; + } + + getSession(s) { + let id = s[0] || s.id || ""; + let username = s[1] || s.username || ""; + let hostname = s[2] || s.hostname || ""; + let platform = s[3] || s.platform || ""; + let alias = s[4] || s.alias || ""; + if (this.style == "list") { + return (); + } + return (); + } + + ["on doubleclick at div.remote-session-link"](evt, me) { + createNewConnect(me.id, "connect"); + } + + ["on click at #menu"](_, me) { + let id = me.parentElement.parentElement.id; + let platform = me.parentElement.parentElement.getAttribute("platform"); + this.$("#rdp").style.setProperty( + "display", (platform == "Windows" && is_win) ? "block" : "none", + ); + this.$("#forget-password").style.setProperty( + "display", handler.xcall("peer_has_password", id) ? "block" : "none", + ); + if (!this.type || this.type == "fav") { + let in_fav = handler.xcall("get_fav").indexOf(id) >= 0; + let el = this.$("add-fav"); + if (el) el.style.setProperty( + "display", in_fav ? "none" : "block", + ); + el = this.$("remove-fav"); + if (el) el.style.setProperty( + "display", in_fav ? "block" : "none", + ); + } + // https://sciter.com/forums/topic/replacecustomize-context-menu/ + let menu = this.$("menu#remote-context"); + menu.setAttribute("remote-id",id); + me.popup(menu); + } + + ["on click at menu#remote-context li"](evt, me) { + let action = me.id; + let id = me.parentElement.getAttribute("remote-id"); + if (action == "connect") { + createNewConnect(id, "connect"); + } else if (action == "transfer") { + createNewConnect(id, "file-transfer"); + } else if (action == "remove") { + if (!this.type) { + handler.xcall("remove_peer", id); + app.componentUpdate(); + } + } else if (action == "forget-password") { + handler.forget_password(id); + } else if (action == "shortcut") { + handler.xcall("create_shortcut", id); + } else if (action == "rdp") { + createNewConnect(id, "rdp"); + } else if (action == "add-fav") { + var favs = handler.get_fav(); + if (favs.indexOf(id) < 0) { + favs = [id].concat(favs); + handler.store_fav(favs); + } + app.multipleSessions.update(); + app.update(); + } else if (action == "remove-fav") { + var favs = handler.get_fav(); + var i = favs.indexOf(id); + favs.splice(i, 1); + handler.store_fav(favs); + app.multipleSessions.update(); + } else if (action == "tunnel") { + createNewConnect(id, "port-forward"); + } else if (action == "rename") { + let old_name = handler.xcall("get_peer_option", id, "alias"); + msgbox("custom-rename", "Rename", "
    \ +
    \ +
    \ + ", + function (res = null) { + if (!res) return; + let name = (res.name || "").trim(); + if (name != old_name) { + handler.xcall("set_peer_option", id, "alias", name); + } + app.update(); + }); + } + } +} + +function getSessionsType() { + return handler.xcall("get_local_option", "show-sessions-type"); +} + +class Favorites extends Element { + render() { + var sessions = handler.xcall("get_fav").map(function(f) { + return handler.xcall("get_peer", f); + }); + return ; + } +} + +export class MultipleSessions extends Element { + render() { + var type = getSessionsType(); + return
    +
    +
    + {translate('Recent Sessions')} + {translate('Favorites')} +
    + {!this.hidden && } + {!this.hidden && } +
    + {!this.hidden && + ((type == "fav" && ) || + )} +
    ; + } + + ["on click at div#sessions-type span.inactive"] (_, el) { + handler.xcall("set_option", 'show-sessions-type', el.id || ""); + this.componentUpdate(); + } + + onSize() { + let w = this.$(".sessions-bar").state.box("width") - 220; + this.$("#sessions-type span").style.setProperty( + "max-width", (w / 2) + "px", + ); + } +} + +document.onsizechange = () => { if (app && app.multipleSessions) app.multipleSessions.onSize(); } diff --git a/src/ui/ab.tis b/src/ui/ab.tis deleted file mode 100644 index 22dc17f3d..000000000 --- a/src/ui/ab.tis +++ /dev/null @@ -1,290 +0,0 @@ -var svg_tile = ; -var svg_list = ; -var search_icon = ; -var clear_icon = ; - -function getSessionsStyleOption(type) { - return (type || "recent") + "-sessions-style"; -} - -function getSessionsStyle(type) { - var v = handler.get_local_option(getSessionsStyleOption(type)); - if (!v) v = type == "ab" ? "list" : "tile"; - return v; -} - -var searchPatterns = {}; - -class SearchBar: Reactor.Component { - this var type = ""; - - function this(params) { - this.type = (params || {}).type || ""; - } - - function render() { - var value = searchPatterns[this.type] || ""; - var me = this; - self.timer(1ms, function() { me.search_id.value = value; }); - return
    - {search_icon} - - {value && {clear_icon}} -
    ; - } - - event click $(span.clear-input) { - this.onChange(''); - } - - event change $(input) (_, el) { - this.onChange(el.value.trim()); - } - - function onChange(v) { - searchPatterns[this.type] = v; - app.multipleSessions.update(); - } -} - -class SessionStyle: Reactor.Component { - this var type = ""; - - function this(params) { - this.type = (params || {}).type || ""; - } - - function render() { - var sessionsStyle = getSessionsStyle(this.type); - return
    - {svg_tile} - {svg_list} -
    ; - } - - event click $(span.inactive) { - var option = getSessionsStyleOption(this.type); - var sessionsStyle = getSessionsStyle(this.type); - handler.set_option(option, sessionsStyle == "tile" ? "list" : "tile"); - app.multipleSessions.update(); - } -} - -class SessionList: Reactor.Component { - this var sessions = []; - this var type = ""; - this var style; - - function this(params) { - this.sessions = params.sessions; - this.type = params.type || ""; - this.style = getSessionsStyle(this.type); - } - - function getSessions() { - var p = searchPatterns[this.type]; - if (!p) return this.sessions; - var tmp = []; - this.sessions.map(function(s) { - var name = s[4] || s.alias || s[0] || s.id || ""; - if (name.indexOf(p) >= 0) tmp.push(s); - }); - return tmp; - } - - function render() { - var sessions = this.getSessions(); - if (sessions.length == 0) { - return
    {translate("Empty")}
    ; - } - var me = this; - sessions = sessions.map(function(x) { return me.getSession(x); }); - return
    - - -
  • {translate('Connect')}
  • -
  • {translate('Transfer File')}
  • -
  • {translate('TCP Tunneling')}
  • -
  • RDP
  • -
    -
  • {translate('Rename')}
  • - {this.type != "fav" &&
  • {translate('Remove')}
  • } - {is_win &&
  • {translate('Create Desktop Shortcut')}
  • } -
  • {translate('Unremember Password')}
  • - {(!this.type || this.type == "fav") &&
  • {translate('Add to Favorites')}
  • } - {(!this.type || this.type == "fav") &&
  • {translate('Remove from Favorites')}
  • } - - - {sessions} -
    ; - } - - function getSession(s) { - var id = s[0] || s.id || ""; - var username = s[1] || s.username || ""; - var hostname = s[2] || s.hostname || ""; - var platform = s[3] || s.platform || ""; - var alias = s[4] || s.alias || ""; - if (this.style == "list") { - return
    -
    - {platform && platformSvg(platform, "white")} -
    -
    -
    -
    {alias ? alias : formatId(id)}
    -
    {username}@{hostname}
    -
    -
    -
    - {svg_menu} -
    -
    ; - } - return
    -
    - {platform && platformSvg(platform, "white")} -
    {username}@{hostname}
    -
    -
    -
    {alias ? alias : formatId(id)}
    - {svg_menu} -
    -
    ; - } - - event dblclick $(div.remote-session-link) (evt, me) { - createNewConnect(me.id, "connect"); - } - - event click $(#menu) (_, me) { - var id = me.parent.parent.id; - var platform = me.parent.parent.attributes["platform"]; - this.$(#rdp).style.set{ - display: (platform == "Windows" && is_win) ? "block" : "none", - }; - this.$(#forget-password).style.set{ - display: handler.peer_has_password(id) ? "block" : "none", - }; - if (!this.type || this.type == "fav") { - var in_fav = handler.get_fav().indexOf(id) >= 0; - this.$(#add-fav).style.set{ - display: in_fav ? "none" : "block", - }; - this.$(#remove-fav).style.set{ - display: in_fav ? "block" : "none", - }; - } - // https://sciter.com/forums/topic/replacecustomize-context-menu/ - var menu = this.$(menu#remote-context); - menu.attributes["remote-id"] = id; - me.popup(menu); - } - - event click $(menu#remote-context li) (evt, me) { - var action = me.id; - var id = me.parent.attributes["remote-id"]; - if (action == "connect") { - createNewConnect(id, "connect"); - } else if (action == "transfer") { - createNewConnect(id, "file-transfer"); - } else if (action == "remove") { - if (!this.type) { - handler.remove_peer(id); - app.update(); - } - } else if (action == "forget-password") { - handler.forget_password(id); - } else if (action == "shortcut") { - handler.create_shortcut(id); - } else if (action == "rdp") { - createNewConnect(id, "rdp"); - } else if (action == "add-fav") { - var favs = handler.get_fav(); - if (favs.indexOf(id) < 0) { - favs = [id].concat(favs); - handler.store_fav(favs); - } - app.multipleSessions.update(); - app.update(); - } else if (action == "remove-fav") { - var favs = handler.get_fav(); - var i = favs.indexOf(id); - favs.splice(i, 1); - handler.store_fav(favs); - app.multipleSessions.update(); - } else if (action == "tunnel") { - createNewConnect(id, "port-forward"); - } else if (action == "rename") { - var old_name = handler.get_peer_option(id, "alias"); - msgbox("custom-rename", "Rename", "
    \ -
    \ -
    \ - ", function(res=null) { - if (!res) return; - var name = (res.name || "").trim(); - if (name != old_name) { - handler.set_peer_option(id, "alias", name); - } - app.update(); - }); - } - } -} - -function getSessionsType() { - return handler.get_local_option("show-sessions-type"); -} - -class Favorites: Reactor.Component { - function render() { - var sessions = handler.get_fav().map(function(f) { - return handler.get_peer(f); - }); - return ; - } -} - -class MultipleSessions: Reactor.Component { - function render() { - var type = getSessionsType(); - return
    -
    -
    - {translate('Recent Sessions')} - {translate('Favorites')} -
    - {!this.hidden && } - {!this.hidden && } -
    - {!this.hidden && - ((type == "fav" && ) || - )} -
    ; - } - - function stupidUpdate() { - /* hidden is workaround of stupid sciter bug */ - this.hidden = true; - this.update(); - var me = this; - self.timer(60ms, function() { - me.hidden = false; - me.update(); - }); - } - - event click $(div#sessions-type span.inactive) (_, el) { - handler.set_option('show-sessions-type', el.id || ""); - this.stupidUpdate(); - } - - function onSize() { - var w = this.$(.sessions-bar).box(#width) - 220; - this.$(#sessions-type span).style.set{ - "max-width": (w / 2) + "px", - }; - } -} - -view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); }); diff --git a/src/ui/cm.css b/src/ui/cm.css index 45f02d116..1a76d4673 100644 --- a/src/ui/cm.css +++ b/src/ui/cm.css @@ -4,11 +4,11 @@ body { div.content { flow: horizontal; - size: *; + size: "*"; } div.left-panel { - size: *; + size: "*"; padding: 1em; border-spacing: 1em; overflow-x: scroll-indicator; @@ -38,7 +38,7 @@ div.chaticon:active { div.right-panel { background: white; border-left: color(border) 1px solid; - size: *; + size: "*"; } div.icon-and-id { @@ -71,7 +71,7 @@ div.permissions > div { } div.permissions icon { - margin: *; + margin: "*"; size: 32px; background-size: cover; background-repeat: no-repeat; @@ -99,7 +99,7 @@ icon.audio { } div.buttons { - width: *; + width: "*"; border-spacing: 0.5em; text-align: center; } @@ -120,14 +120,14 @@ button#disconnect:active { opacity: 0.5; } -@media platform != "OSX" { +@media not_osx { header .window-toolbar { left: 40px; top: 8px; } } -@media platform == "OSX" { +@media is_osx { header .tabs-wrapper { margin-left: 80px; margin-top: 8px; @@ -135,13 +135,13 @@ header .tabs-wrapper { } div.tabs-wrapper { - size: *; + size: "*"; position: relative; overflow: hidden; } div.tabs { - size: *; + size: "*"; flow: horizontal; white-space: nowrap; overflow: hidden; @@ -156,7 +156,7 @@ div.border-bottom { position: absolute; bottom: 0; left: 0; - width: *; + width: "*"; height: 1px; background: color(border) 1px solid; } @@ -213,7 +213,7 @@ div.tab-arrows { div.tab-arrows span { display: inline-block; - height: *; + height: "*"; margin: 0; padding: 6px 2px; } diff --git a/src/ui/cm.html b/src/ui/cm.html index 4edb4a762..b6ff7fb25 100644 --- a/src/ui/cm.html +++ b/src/ui/cm.html @@ -1,20 +1,15 @@ - - + + +
    -
    +
    - - - +
    +
    +
    diff --git a/src/ui/cm.js b/src/ui/cm.js new file mode 100644 index 000000000..b966248d3 --- /dev/null +++ b/src/ui/cm.js @@ -0,0 +1,383 @@ +import { handler,view,is_osx,string2RGB,adjustBorder,svg_chat,translate,ChatBox,getNowStr,setWindowButontsAndIcon,is_linux } from "./common.js"; +import {$} from "@sciter"; +// TODO in sciterjs window-frame +// view.windowFrame = is_osx ? #extended : #solid; + +var body; +var connections = []; +var show_chat = false; + +class Body extends Element { + cur = 0; + + this() { + body = this; + } + + render() { + if (connections.length == 0) return
    ; + let c = connections[this.cur]; + this.connection = c; + this.cid = c.id; + let auth = c.authorized; + let callback = (msg)=> { + this.sendMsg(msg); + }; + setTimeout(adaptSize, 1); + let right_style = show_chat ? "" : "display: none"; + return (
    +
    +
    +
    + {c.name[0].toUpperCase()} +
    +
    +
    {c.name}
    +
    ({c.peer_id})
    +
    {translate('Connected')} {" "} {getElaspsed(c.time)}
    +
    +
    +
    + {c.is_file_transfer || c.port_forward ? "" :
    {translate('Permissions')}
    } + {c.is_file_transfer || c.port_forward ? "" :
    +
    +
    +
    +
    } + {c.port_forward ?
    Port Forwarding: {c.port_forward}
    : ""} +
    +
    + {auth ? "" : } + {auth ? "" : } + {auth ? : ""} +
    + {c.is_file_transfer || c.port_forward ? "" :
    {svg_chat}
    } +
    +
    + {c.is_file_transfer || c.port_forward ? "" : } +
    +
    ); + } + + sendMsg(text) { + if (!text) return; + let { cid, connection } = this; + checkClickTime(function() { + connection.msgs.push({ name: "me", text: text, time: getNowStr()}); + handler.xcall("send_msg",cid, text); + body.componentUpdate(); + }); + } + + ["on click at icon.keyboard"](e) { + let { cid, connection } = this; + checkClickTime(function() { + connection.keyboard = !connection.keyboard; + body.componentUpdate(); + handler.xcall("switch_permission",cid, "keyboard", connection.keyboard); + }); + } + + ["on click at icon.clipboard"]() { + let { cid, connection } = this; + checkClickTime(function() { + connection.clipboard = !connection.clipboard; + body.componentUpdate(); + handler.xcall("switch_permission",cid, "clipboard", connection.clipboard); + }); + } + + ["on click at icon.audio"]() { + let { cid, connection } = this; + checkClickTime(function() { + connection.audio = !connection.audio; + body.componentUpdate(); + handler.xcall("switch_permission",cid, "audio", connection.audio); + }); + } + + ["on click at button#accept"]() { + let { cid, connection } = this; + checkClickTime(function() { + connection.authorized = true; + body.componentUpdate(); + handler.xcall("authorize",cid); + setTimeout(()=>view.state = Window.WINDOW_MINIMIZED,30); + }); + } + + ["on click at button#dismiss"]() { + let cid = this.cid; + checkClickTime(function() { + handler.close(cid); // TEST + }); + } + + ["on click at button#disconnect"]() { + let cid = this.cid; + checkClickTime(function() { + handler.close(cid); // TEST + }); + } + ["on click at div.chaticon"]() { + checkClickTime(function() { + show_chat = !show_chat; + adaptSize(); + }); + } +} + +$("body").content(); + +var header; + +class Header extends Element { + this() { + header = this; + } + + render() { + let me = this; + let conn = connections[body.cur]; + if (conn && conn.unreaded > 0) { + let el = this.select("#unreaded" + conn.id); // TODO select + if (el) el.style.setProperty("display","inline-block"); + setTimeout(function() { + conn.unreaded = 0; + let el = this.select("#unreaded" + conn.id); // TODO + if (el) el.style.setProperty("display","none"); + },300); + } + let tabs = connections.map((c, i)=> this.renderTab(c, i)); + return (
    + {tabs} +
    +
    + < + > +
    +
    ); + } + + renderTab(c, i) { + let cur = body.cur; + return (
    + {c.name} + {c.unreaded > 0 ? {c.unreaded} : ""} +
    ); + } + + update_cur(idx) { + checkClickTime(function(){ + body.cur = idx; + update(); + setTimeout(adjustHeader,1); + }); + } + + ["on click at div.tab"] (_, me) { + let idx = me.index; + if (idx == body.cur) return; + this.update_cur(idx); + } + + ["on click at span.left-arrow"]() { + let cur = body.cur; + if (cur == 0) return; + this.update_cur(cur - 1); + } + + ["on click at span.right-arrow"]() { + let cur = body.cur; + if (cur == connections.length - 1) return; + this.update_cur(cur + 1); + } +} + +if (is_osx) { + $("header").content(
    ); + $("header").attributes["role"] = "window-caption"; // TODO +} else { + $("div.window-toolbar").content(
    ); + setWindowButontsAndIcon(true); +} + +function checkClickTime(callback) { + callback(); +} + +function adaptSize() { + $("div.right-panel").style.setProperty("display",show_chat ? "block" : "none"); + let el = $("div.chaticon"); + if (el) el.classList.toggle("active", show_chat); + let [x, y, w, h] = view.state.box("rectw", "border", "screen"); + if (show_chat && w < 600) { + view.move(x - (600 - w), y, 600, h); + } else if (!show_chat && w > 450) { + view.move(x + (w - 300), y, 300, h); + } +} + +function update() { + header.componentUpdate(); + body.componentUpdate(); +} + +function bring_to_top(idx=-1) { + if (view.state == Window.WINDOW_HIDDEN || view.state == Window.WINDOW_MINIMIZED) { + if (is_linux) { + view.focus = $("body"); + } else { + view.state = Window.WINDOW_SHOWN; + } + if (idx >= 0) body.cur = idx; + } else { + view.isTopmost = true; // TEST + view.isTopmost = false; // TEST + } +} + +handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio) { + let conn; + connections.map(function(c) { + if (c.id == id) conn = c; + }); + if (conn) { + conn.authorized = authorized; + update(); + return; + } + if (!name) name = "NA"; + connections.push({ + id: id, is_file_transfer: is_file_transfer, peer_id: peer_id, + port_forward: port_forward, + name: name, authorized: authorized, time: new Date(), + keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0, + audio: audio, + }); + body.cur = connections.length - 1; + bring_to_top(); + update(); + setTimeout(adjustHeader,1); + if (authorized) { + setTimeout(()=>view.state = Window.WINDOW_MINIMIZED,3000); + } +} + +handler.removeConnection = function(id) { + let i = -1; + connections.map(function(c, idx) { + if (c.id == id) i = idx; + }); + connections.splice(i, 1); + if (connections.length == 0) { + handler.xcall("exit"); + } else { + if (body.cur >= i && body.cur > 0) body.cur -= 1; + update(); + } +} + +handler.newMessage = function(id, text) { + let idx = -1; + connections.map(function(c, i) { + if (c.id == id) idx = i; + }); + let conn = connections[idx]; + if (!conn) return; + conn.msgs.push({name: conn.name, text: text, time: getNowStr()}); + bring_to_top(idx); + if (idx == body.cur) show_chat = true; + conn.unreaded += 1; + update(); +} + +handler.awake = function() { + view.state = Window.WINDOW_SHOWN; + view.focus = $("body"); +} + +// TEST +// view << event statechange { +// adjustBorder(); +// } +view.on("statechange",()=>{ + adjustBorder(); +}) + +document.on("ready",()=>{ + adjustBorder(); + let [sw, sh] = view.screenBox("workarea", "dimension"); + let w = 300; + let h = 400; + view.move(sw - w, 0, w, h); +}) + +document.on("unloadequest",(evt)=>{ + view.state = Window.WINDOW_HIDDEN; + console.log("cm unloadequest") + evt.preventDefault(); // prevent unloading TEST +}) + +function getElaspsed(time) { + // let now = new Date(); + // let seconds = Date.diff(time, now, #seconds); + // let hours = seconds / 3600; + // let days = hours / 24; + // hours = hours % 24; + // let minutes = seconds % 3600 / 60; + // seconds = seconds % 60; + // let out = String.printf("%02d:%02d:%02d", hours, minutes, seconds); + // if (days > 0) { + // out = String.printf("%d day%s %s", days, days > 1 ? "s" : "", out); + // } + let out = "TIME TODO" + new Date(); // TODO + return out; +} + +// updateTime +setInterval(function() { + let el = $("#time"); + if (el) { + let c = connections[body.cur]; + if (c) { + el.text = getElaspsed(c.time); + } + } +},1000); + + +function adjustHeader() { + let hw = $("header").state.box("width"); + let tabswrapper = $("div.tabs-wrapper"); + let tabs = $("div.tabs"); + let arrows = $("div.tab-arrows"); + if (!arrows) return; + let n = connections.length; + let wtab = 80; + let max = hw - 98; + let need_width = n * wtab + 2; // include border of active tab + if (need_width < max) { + arrows.style.setProperty("display","none"); + tabs.style.setProperty("width",need_width); + tabs.style.setProperty("margin-left",0); + tabswrapper.style.setProperty("width",need_width); + } else { + let margin = (body.cur + 1) * wtab - max + 30; + if (margin < 0) margin = 0; + arrows.style.setProperty("display","block"); + tabs.style.setProperty("width",(max - 20 + margin) + 'px'); + tabs.style.setProperty("margin-left",-margin + 'px'); + tabswrapper.style.setProperty("width",(max + 10) + 'px'); + } +} + +document.onsizechange = ()=>{ + console.log("cm onsizechange"); + adjustHeader(); +} + +// handler.addConnection(0, false, 0, "", "test1", true, false, false, false); +// handler.addConnection(1, false, 0, "", "test2--------", true, false, false, false); +// handler.addConnection(2, false, 0, "", "test3", true, false, false, false); +// handler.newMessage(0, 'h'); diff --git a/src/ui/cm.rs b/src/ui/cm.rs index 77ec90ff8..7873ea14c 100644 --- a/src/ui/cm.rs +++ b/src/ui/cm.rs @@ -91,13 +91,13 @@ impl ConnectionManager { clipboard, audio ), - ); + ); // TODO self.write().unwrap().senders.insert(id, tx); } fn remove_connection(&self, id: i32) { self.write().unwrap().senders.remove(&id); - self.call("removeConnection", &make_args!(id)); + self.call("removeConnection", &make_args!(id)); // TODO } async fn handle_data( diff --git a/src/ui/cm.tis b/src/ui/cm.tis deleted file mode 100644 index 71a8e39a6..000000000 --- a/src/ui/cm.tis +++ /dev/null @@ -1,401 +0,0 @@ -view.windowFrame = is_osx ? #extended : #solid; - -var body; -var connections = []; -var show_chat = false; - -class Body: Reactor.Component -{ - this var cur = 0; - - function this() { - body = this; - } - - function render() { - if (connections.length == 0) return
    ; - var c = connections[this.cur]; - this.connection = c; - this.cid = c.id; - var auth = c.authorized; - var me = this; - var callback = function(msg) { - me.sendMsg(msg); - }; - self.timer(1ms, adaptSize); - var right_style = show_chat ? "" : "display: none"; - return
    -
    -
    -
    - {c.name[0].toUpperCase()} -
    -
    -
    {c.name}
    -
    ({c.peer_id})
    -
    {translate('Connected')} {" "} {getElaspsed(c.time)}
    -
    -
    -
    - {c.is_file_transfer || c.port_forward ? "" :
    {translate('Permissions')}
    } - {c.is_file_transfer || c.port_forward ? "" :
    -
    -
    -
    -
    } - {c.port_forward ?
    Port Forwarding: {c.port_forward}
    : ""} -
    -
    - {auth ? "" : } - {auth ? "" : } - {auth ? : ""} -
    - {c.is_file_transfer || c.port_forward ? "" :
    {svg_chat}
    } -
    -
    - {c.is_file_transfer || c.port_forward ? "" : } -
    -
    ; - } - - function sendMsg(text) { - if (!text) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.msgs.push({ name: "me", text: text, time: getNowStr()}); - handler.send_msg(cid, text); - body.update(); - }); - } - - event click $(icon.keyboard) (e) { - var { cid, connection } = this; - checkClickTime(function() { - connection.keyboard = !connection.keyboard; - body.update(); - handler.switch_permission(cid, "keyboard", connection.keyboard); - }); - } - - event click $(icon.clipboard) { - var { cid, connection } = this; - checkClickTime(function() { - connection.clipboard = !connection.clipboard; - body.update(); - handler.switch_permission(cid, "clipboard", connection.clipboard); - }); - } - - event click $(icon.audio) { - var { cid, connection } = this; - checkClickTime(function() { - connection.audio = !connection.audio; - body.update(); - handler.switch_permission(cid, "audio", connection.audio); - }); - } - - event click $(button#accept) { - var { cid, connection } = this; - checkClickTime(function() { - connection.authorized = true; - body.update(); - handler.authorize(cid); - self.timer(30ms, function() { - view.windowState = View.WINDOW_MINIMIZED; - }); - }); - } - - event click $(button#dismiss) { - var cid = this.cid; - checkClickTime(function() { - handler.close(cid); - }); - } - - event click $(button#disconnect) { - var cid = this.cid; - checkClickTime(function() { - handler.close(cid); - }); - } -} - -$(body).content(); - -var header; - -class Header: Reactor.Component -{ - function this() { - header = this; - } - - function render() { - var me = this; - var conn = connections[body.cur]; - if (conn && conn.unreaded > 0) {; - var el = me.select("#unreaded" + conn.id); - if (el) el.style.set { - display: "inline-block", - }; - self.timer(300ms, function() { - conn.unreaded = 0; - var el = me.select("#unreaded" + conn.id); - if (el) el.style.set { - display: "none", - }; - }); - } - var tabs = connections.map(function(c, i) { return me.renderTab(c, i) }); - return
    - {tabs} -
    -
    - < - > -
    -
    ; - } - - function renderTab(c, i) { - var cur = body.cur; - return
    - {c.name} - {c.unreaded > 0 ? {c.unreaded} : ""} -
    ; - } - - function update_cur(idx) { - checkClickTime(function() { - body.cur = idx; - update(); - self.timer(1ms, adjustHeader); - }); - } - - event click $(div.tab) (_, me) { - var idx = me.index; - if (idx == body.cur) return; - this.update_cur(idx); - } - - event click $(span.left-arrow) { - var cur = body.cur; - if (cur == 0) return; - this.update_cur(cur - 1); - } - - event click $(span.right-arrow) { - var cur = body.cur; - if (cur == connections.length - 1) return; - this.update_cur(cur + 1); - } -} - -if (is_osx) { - $(header).content(
    ); - $(header).attributes["role"] = "window-caption"; -} else { - $(div.window-toolbar).content(
    ); - setWindowButontsAndIcon(true); -} - -event click $(div.chaticon) { - checkClickTime(function() { - show_chat = !show_chat; - adaptSize(); - }); -} - -function checkClickTime(callback) { - callback(); -} - -function adaptSize() { - $(div.right-panel).style.set { - display: show_chat ? "block" : "none", - }; - var el = $(div.chaticon); - if (el) el.attributes.toggleClass("active", show_chat); - var (x, y, w, h) = view.box(#rectw, #border, #screen); - if (show_chat && w < 600) { - view.move(x - (600 - w), y, 600, h); - } else if (!show_chat && w > 450) { - view.move(x + (w - 300), y, 300, h); - } -} - -function update() { - header.update(); - body.update(); -} - -function bring_to_top(idx=-1) { - if (view.windowState == View.WINDOW_HIDDEN || view.windowState == View.WINDOW_MINIMIZED) { - if (is_linux) { - view.focus = self; - } else { - view.windowState = View.WINDOW_SHOWN; - } - if (idx >= 0) body.cur = idx; - } else { - view.windowTopmost = true; - view.windowTopmost = false; - } -} - -handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio) { - var conn; - connections.map(function(c) { - if (c.id == id) conn = c; - }); - if (conn) { - conn.authorized = authorized; - update(); - return; - } - if (!name) name = "NA"; - connections.push({ - id: id, is_file_transfer: is_file_transfer, peer_id: peer_id, - port_forward: port_forward, - name: name, authorized: authorized, time: new Date(), - keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0, - audio: audio, - }); - body.cur = connections.length - 1; - bring_to_top(); - update(); - self.timer(1ms, adjustHeader); - if (authorized) { - self.timer(3s, function() { - view.windowState = View.WINDOW_MINIMIZED; - }); - } -} - -handler.removeConnection = function(id) { - var i = -1; - connections.map(function(c, idx) { - if (c.id == id) i = idx; - }); - connections.splice(i, 1); - if (connections.length == 0) { - handler.exit(); - } else { - if (body.cur >= i && body.cur > 0) body.cur -= 1; - update(); - } -} - -handler.newMessage = function(id, text) { - var idx = -1; - connections.map(function(c, i) { - if (c.id == id) idx = i; - }); - var conn = connections[idx]; - if (!conn) return; - conn.msgs.push({name: conn.name, text: text, time: getNowStr()}); - bring_to_top(idx); - if (idx == body.cur) show_chat = true; - conn.unreaded += 1; - update(); -} - -handler.awake = function() { - view.windowState = View.WINDOW_SHOWN; - view.focus = self; -} - -view << event statechange { - adjustBorder(); -} - -function self.ready() { - adjustBorder(); - var (sw, sh) = view.screenBox(#workarea, #dimension); - var w = 300; - var h = 400; - view.move(sw - w, 0, w, h); -} - -function getElaspsed(time) { - var now = new Date(); - var seconds = Date.diff(time, now, #seconds); - var hours = seconds / 3600; - var days = hours / 24; - hours = hours % 24; - var minutes = seconds % 3600 / 60; - seconds = seconds % 60; - var out = String.printf("%02d:%02d:%02d", hours, minutes, seconds); - if (days > 0) { - out = String.printf("%d day%s %s", days, days > 1 ? "s" : "", out); - } - return out; -} - -function updateTime() { - self.timer(1s, function() { - var el = $(#time); - if (el) { - var c = connections[body.cur]; - if (c) { - el.text = getElaspsed(c.time); - } - } - updateTime(); - }); -} - -updateTime(); - -function self.closing() { - view.windowState = View.WINDOW_HIDDEN; - return false; -} - - -function adjustHeader() { - var hw = $(header).box(#width); - var tabswrapper = $(div.tabs-wrapper); - var tabs = $(div.tabs); - var arrows = $(div.tab-arrows); - if (!arrows) return; - var n = connections.length; - var wtab = 80; - var max = hw - 98; - var need_width = n * wtab + 2; // include border of active tab - if (need_width < max) { - arrows.style.set { - display: "none", - }; - tabs.style.set { - width: need_width, - margin-left: 0, - }; - tabswrapper.style.set { - width: need_width, - }; - } else { - var margin = (body.cur + 1) * wtab - max + 30; - if (margin < 0) margin = 0; - arrows.style.set { - display: "block", - }; - tabs.style.set { - width: (max - 20 + margin) + 'px', - margin-left: -margin + 'px' - }; - tabswrapper.style.set { - width: (max + 10) + 'px', - }; - } -} - -view.on("size", adjustHeader); - -// handler.addConnection(0, false, 0, "", "test1", true, false, false, false); -// handler.addConnection(1, false, 0, "", "test2--------", true, false, false, false); -// handler.addConnection(2, false, 0, "", "test3", true, false, false, false); -// handler.newMessage(0, 'h'); diff --git a/src/ui/common.js b/src/ui/common.js new file mode 100644 index 000000000..240fdc591 --- /dev/null +++ b/src/ui/common.js @@ -0,0 +1,379 @@ + +export const view = Window.this; +export const handler = document.$("#handler") || view; + +try { view.windowIcon = document.url(handler.xcall("get_icon")); } catch (e) { } + +export const OS = view.mediaVar("platform"); +export const is_osx = OS == "OSX"; +export const is_win = OS == "Windows"; +export const is_linux = OS == "Linux"; + +view.mediaVar("is_osx", is_osx); +view.mediaVar("not_osx", !is_osx); +handler.is_port_forward = false; +handler.is_file_transfer = false; +export var is_xfce = false; +try { is_xfce = handler.xcall("is_xfce"); } catch (e) { } + + +export function translate(name) { + try { + return handler.xcall("t", name); + } catch (_) { + return name; + } +} + +export function hashCode(str) { + let hash = 160 << 16 + 114 << 8 + 91; + for (let i = 0; i < str.length; i += 1) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + return hash % 16777216; +} + +export function intToRGB(i, a = 1) { + return `rgba(${((i >> 16) & 0xFF)}, ${((i >> 8) & 0x7F)},${(i & 0xFF)},${a})`; +} + +export function string2RGB(s, a = 1) { + return intToRGB(hashCode(s), a); +} + +export function getTime() { + return new Date().valueOf(); +} + +export function platformSvg(platform, color) { + platform = (platform || "").toLowerCase(); + if (platform == "linux") { + return ( + + + + + ); + } + if (platform == "mac os") { + return ( + + ); + } + return ( + + ); +} + +export function centerize(w, h) { + let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw"); + if (w > sw) w = sw; + if (h > sh) h = sh; + const x = (sx + sw - w) / 2; + const y = (sy + sh - h) / 2; + view.move(x, y, w, h); +} + +// TODO CSS +export function setWindowButontsAndIcon(only_min = false) { + if (only_min) { + document.$("div.window-buttons").content( +
    + +
    + ); + } else { + document.$("div.window-buttons").content(
    + + + +
    ); + } + document.$("div.window-icon>icon").style.setProperty( + "background-image", `url(${handler.xcall("get_icon")})`, + ); +} + +export function adjustBorder() { + if (is_osx) { + let headerStyle = document.$("header").style; + if (view.state == Window.WINDOW_FULL_SCREEN) { + headerStyle.setProperty("display", "none",); + } else { + headerStyle.setProperty("display", "block",); + headerStyle.setProperty("padding", "0",); + } + return; + } + if (view.state == Window.WINDOW_MAXIMIZED) { + document.style.setProperty("border", "window-frame-width solid transparent"); + } else if (view.state == Window.WINDOW_FULL_SCREEN) { + document.style.setProperty("border", "none"); + } else { + document.style.setProperty("border", "black solid 1px"); + } + let el = document.$("button#maximize"); + if (el) el.classList.toggle("restore", view.state == Window.WINDOW_MAXIMIZED); + el = document.$("span#fullscreen"); + if (el) el.classList.toggle("active", view.state == Window.WINDOW_FULL_SCREEN); +} + +export const svg_checkmark = (); +export const svg_edit = ( + +); +export const svg_eye = ( + + +); +export const svg_send = ( + +); +export const svg_chat = ( + +); + +export function scrollToBottom(el) { + // TEST .box() + let y = el.state.box("height", "content") - el.state.box("height", "client"); + el.scrollTo(0, y); +} + +export function getNowStr() { + let now = new Date(); + return String.printf("%02d:%02d:%02d", now.hour, now.minute, now.second); +} + +/******************** start of chatbox ****************************************/ +export class ChatBox extends Element { + msgs = []; + callback; + + this(props) { + if (props) { + this.msgs = props.msgs || []; + this.callback = props.callback; + } + } + + renderMsg(msg) { + let cls = msg.name == "me" ? "right-side msg" : "left-side msg"; + return ( +
    + {msg.name == "me" ? +
    {msg.time + " "} me
    : +
    {msg.name} {" " + msg.time}
    + } +
    {msg.text}
    +
    + ); + } + + render() { + let msgs = this.msgs.map((msg) => this.renderMsg(msg)); + setTimeout(() => { + scrollToBottom(this.msgs); + }, 1); + // TODO @{this.msgs} in TIS: + return (
    + + {msgs} + +
    + + {svg_send} +
    +
    ); + } + + send() { + let el = this.$("input"); + let value = (el.value || "").trim(); + el.value = ""; + if (!value) return; + if (this.callback) this.callback(value); + } + + ["on keydown at input"](evt) { + // TODO is shortcutKey useless? + if (!evt.shortcutKey) { + // TODO TEST Windows/Mac + if (evt.code == "KeyRETURN") { + this.send(); + } + } + } + + ["on click at div.send span"](evt) { + this.send(); + view.focus = this.$("input"); + } +} +/******************** end of chatbox ****************************************/ + +/******************** start of msgbox ****************************************/ +var remember_password = false; +var msgbox_params; +function getMsgboxParams() { + return msgbox_params; +} + +// tmp workaround https://sciter.com/forums/topic/menu-not-be-hidden-when-open-dialog-on-linux/ +export function msgbox(type, title, text, callback = null, height = 180, width = 500, retry = 0, contentStyle = "") { + if (is_linux) { // fix menu not hidden issue + setTimeout(() => msgbox_(type, title, text, callback, height, width, retry, contentStyle), 1); + } else { + msgbox_(type, title, text, callback, height, width, retry, contentStyle); + } +} + +function msgbox_(type, title, text, callback, height, width, retry, contentStyle) { + let has_msgbox = msgbox_params != null; + if (!has_msgbox && !type) return; + let remember = false; + try { + remember = handler.xcall("get_remember"); + } catch (e) { } + msgbox_params = { + remember: remember, type: type, text: text, title: title, + getParams: getMsgboxParams, + callback: callback, translate: translate, + retry: retry, contentStyle: contentStyle, + }; + if (has_msgbox) return; + let dialog = { + client: true, + parameters: msgbox_params, + width: width + (is_xfce ? 50 : 0), + height: height + (is_xfce ? 50 : 0), + }; + let html = handler.xcall("get_msgbox"); + if (html) dialog.html = html; + else dialog.url = document.url("msgbox.html"); + let res = view.modal(dialog); + msgbox_params = null; + console.log(`msgbox return, type: ${type}, res: ${res}`); + if (type.indexOf("custom") >= 0) { + // + } else if (!res) { + if (!handler.is_port_forward) view.close(); + } else if (res == "!alive") { + // do nothing + } else if (res.type == "input-password") { + if (!handler.is_port_forward) msgbox("connecting", "Connecting...", "Logging in..."); + handler.login(res.password, res.remember); + if (!is_port_forward) msgbox("connecting", "Connecting...", "Logging in..."); + } else if (res.reconnect) { + if (!handler.is_port_forward) connecting(); + handler.reconnect(); + } +} + +export function connecting() { + handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait."); +} + +handler.msgbox = function (type, title, text, retry = 0) { + setTimeout(() => msgbox(type, title, text, null, 180, 500, retry), 30); +} + +var reconnectTimeout = 1; +handler.msgbox_retry = function (type, title, text, hasRetry) { + handler.msgbox(type, title, text, hasRetry ? reconnectTimeout : 0); + if (hasRetry) { + reconnectTimeout *= 2; + } else { + reconnectTimeout = 1; + } +} +/******************** end of msgbox ****************************************/ +// TODO Progress +// function Progress() +// { +// var _val; +// var pos = -0.25; + +// function step() { +// if( _val !== undefined ) { this.refresh(); return false; } +// pos += 0.02; +// if( pos > 1.25) +// pos = -0.25; +// this.refresh(); +// return true; +// } + +// function paintNoValue(gfx) +// { +// var (w,h) = this.box(#dimension,#inner); +// var x = pos * w; +// w = w * 0.25; +// gfx.fillColor( this.style#color ) +// .pushLayer(#inner-box) +// .rectangle(x,0,w,h) +// .popLayer(); +// return true; +// } + +// this[#value] = property(v) { +// get return _val; +// set { +// _val = undefined; +// pos = -0.25; +// this.paintContent = paintNoValue; +// this.animate(step); +// this.refresh(); +// } +// } + +// this.value = ""; +// } + +const svg_eye_cross = ( + + +); + +export class PasswordComponent extends Element { + visible = false; + value = ''; + name = 'password'; + + constructor(props) { + if (props && props.value) { + this.value = props.value; + } + if (props && props.name) { + this.name = props.name; + } + } + + render() { + return ( +
    + + {this.visible ? svg_eye_cross : svg_eye} +
    ); + } + + ["on click at svg"](svg) { + let el = this.$("input"); + let value = el.value; + // TODO selectionStart/selectionEnd run ok,but always return 0 + let start = el.xcall("selectionStart") || 0; + let end = el.xcall("selectionEnd"); + this.componentUpdate({ visible: !this.visible }); + setTimeout(() => { + let el = this.$("input"); + view.focus = el; + el.value = value; + el.xcall("setSelection", start, end); + }, 30) + } +} + +export function isReasonableSize(r) { + let x = r[0]; + let y = r[1]; + return !(x < -3200 || x > 3200 || y < -3200 || y > 3200); +} + diff --git a/src/ui/common.tis b/src/ui/common.tis deleted file mode 100644 index b4e0bf190..000000000 --- a/src/ui/common.tis +++ /dev/null @@ -1,378 +0,0 @@ -include "sciter:reactor.tis"; - -var handler = $(#handler) || view; -try { view.windowIcon = self.url(handler.get_icon()); } catch(e) {} -var OS = view.mediaVar("platform"); -var is_osx = OS == "OSX"; -var is_win = OS == "Windows"; -var is_linux = OS == "Linux"; -var is_file_transfer; -var is_xfce = false; -try { is_xfce = handler.is_xfce(); } catch(e) {} - - -function translate(name) { - try { - return handler.t(name); - } catch(_) { - return name; - } -} - -function hashCode(str) { - var hash = 160 << 16 + 114 << 8 + 91; - for (var i = 0; i < str.length; i += 1) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - } - return hash % 16777216; -} - -function intToRGB(i, a = 1) { - return 'rgba(' + ((i >> 16) & 0xFF) + ', ' + ((i >> 8) & 0x7F) - + ',' + (i & 0xFF) + ',' + a + ')'; -} - -function string2RGB(s, a = 1) { - return intToRGB(hashCode(s), a); -} - -function getTime() { - var now = new Date(); - return now.valueOf(); -} - -function platformSvg(platform, color) { - platform = (platform || "").toLowerCase(); - if (platform == "linux") { - return - - - - - ; - } - if (platform == "mac os") { - return - - ; - } - return - - ; -} - -function centerize(w, h) { - var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw); - if (w > sw) w = sw; - if (h > sh) h = sh; - var x = (sx + sw - w) / 2; - var y = (sy + sh - h) / 2; - view.move(x, y, w, h); -} - -function setWindowButontsAndIcon(only_min=false) { - if (only_min) { - $(div.window-buttons).content(
    -
    -
    ); - } else { - $(div.window-buttons).content(
    -
    -
    -
    -
    ); - } - $(div.window-icon>icon).style.set { - "background-image": "url('" + handler.get_icon() + "')", - }; -} - -function adjustBorder() { - if (is_osx) { - if (view.windowState == View.WINDOW_FULL_SCREEN) { - $(header).style.set { - display: "none", - }; - } else { - $(header).style.set { - display: "block", - padding: "0", - }; - } - return; - } - if (view.windowState == view.WINDOW_MAXIMIZED) { - self.style.set { - border: "window-frame-width solid transparent", - }; - } else if (view.windowState == view.WINDOW_FULL_SCREEN) { - self.style.set { - border: "none", - }; - } else { - self.style.set { - border: "black solid 1px", - }; - } - var el = $(button#maximize); - if (el) el.attributes.toggleClass("restore", view.windowState == View.WINDOW_MAXIMIZED); - el = $(span#fullscreen); - if (el) el.attributes.toggleClass("active", view.windowState == View.WINDOW_FULL_SCREEN); -} - -var svg_checkmark = ; -var svg_edit = - -; -var svg_eye = - - -; -var svg_send = - -; -var svg_chat = - -; - -function scrollToBottom(el) { - var y = el.box(#height, #content) - el.box(#height, #client); - el.scrollTo(0, y); -} - -function getNowStr() { - var now = new Date(); - return String.printf("%02d:%02d:%02d", now.hour, now.minute, now.second); -} - -/******************** start of chatbox ****************************************/ -class ChatBox: Reactor.Component { - this var msgs = []; - this var callback; - - function this(params) { - if (params) { - this.msgs = params.msgs || []; - this.callback = params.callback; - } - } - - function renderMsg(msg) { - var cls = msg.name == "me" ? "right-side msg" : "left-side msg"; - return
    - {msg.name == "me" ? -
    {msg.time + " "} me
    : -
    {msg.name} {" " + msg.time}
    - } -
    {msg.text}
    -
    ; - } - - function render() { - var me = this; - var msgs = this.msgs.map(function(msg) { return me.renderMsg(msg); }); - self.timer(1ms, function() { - scrollToBottom(me.msgs); - }); - return
    - - {msgs} - -
    - - {svg_send} -
    -
    ; - } - - function send() { - var el = this.$(input); - var value = (el.value || "").trim(); - el.value = ""; - if (!value) return; - if (this.callback) this.callback(value); - } - - event keydown $(input) (evt) { - if (!evt.shortcutKey) { - if (evt.keyCode == Event.VK_ENTER || - (view.mediaVar("platform") == "OSX" && evt.keyCode == 0x4C)) { - this.send(); - } - } - } - - event click $(div.send span) { - this.send(); - view.focus = $(input); - } -} -/******************** end of chatbox ****************************************/ - -/******************** start of msgbox ****************************************/ -var remember_password = false; -var msgbox_params; -function getMsgboxParams() { - return msgbox_params; -} - -// tmp workaround https://sciter.com/forums/topic/menu-not-be-hidden-when-open-dialog-on-linux/ -function msgbox(type, title, text, callback=null, height=180, width=500, retry=0, contentStyle="") { - if (is_linux) { // fix menu not hidden issue - self.timer(1ms, - function() { - msgbox_(type, title, text, callback, height, width, retry, contentStyle); - }); - } else { - msgbox_(type, title, text, callback, height, width, retry, contentStyle); - } -} - -function msgbox_(type, title, text, callback, height, width, retry, contentStyle) { - var has_msgbox = msgbox_params != null; - if (!has_msgbox && !type) return; - var remember = false; - try { - remember = handler.get_remember(); - } catch(e) {} - msgbox_params = { - remember: remember, type: type, text: text, title: title, - getParams: getMsgboxParams, - callback: callback, translate: translate, - retry: retry, contentStyle: contentStyle, - }; - if (has_msgbox) return; - var dialog = { - client: true, - parameters: msgbox_params, - width: width + (is_xfce ? 50 : 0), - height: height + (is_xfce ? 50 : 0), - }; - var html = handler.get_msgbox(); - if (html) dialog.html = html; - else dialog.url = self.url("msgbox.html"); - var res = view.dialog(dialog); - msgbox_params = null; - stdout.printf("msgbox return, type: %s, res: %s\n", type, res); - if (type.indexOf("custom") >= 0) { - // - } else if (!res) { - if (!is_port_forward) view.close(); - } else if (res == "!alive") { - // do nothing - } else if (res.type == "input-password") { - handler.login(res.password, res.remember); - if (!is_port_forward) msgbox("connecting", "Connecting...", "Logging in..."); - } else if (res.reconnect) { - if (!is_port_forward) connecting(); - handler.reconnect(); - } -} - -function connecting() { - handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait."); -} - -handler.msgbox = function(type, title, text, retry=0) { - self.timer(30ms, function() { msgbox(type, title, text, null, 180, 500, retry); }); -} - -var reconnectTimeout = 1; -handler.msgbox_retry = function(type, title, text, hasRetry) { - handler.msgbox(type, title, text, hasRetry ? reconnectTimeout : 0); - if (hasRetry) { - reconnectTimeout *= 2; - } else { - reconnectTimeout = 1; - } -} -/******************** end of msgbox ****************************************/ - -function Progress() -{ - var _val; - var pos = -0.25; - - function step() { - if( _val !== undefined ) { this.refresh(); return false; } - pos += 0.02; - if( pos > 1.25) - pos = -0.25; - this.refresh(); - return true; - } - - function paintNoValue(gfx) - { - var (w,h) = this.box(#dimension,#inner); - var x = pos * w; - w = w * 0.25; - gfx.fillColor( this.style#color ) - .pushLayer(#inner-box) - .rectangle(x,0,w,h) - .popLayer(); - return true; - } - - this[#value] = property(v) { - get return _val; - set { - _val = undefined; - pos = -0.25; - this.paintContent = paintNoValue; - this.animate(step); - this.refresh(); - } - } - - this.value = ""; -} - -var svg_eye_cross = - - -; - -class PasswordComponent: Reactor.Component { - this var visible = false; - this var value = ''; - this var name = 'password'; - - function this(params) { - if (params && params.value) { - this.value = params.value; - } - if (params && params.name) { - this.name = params.name; - } - } - - function render() { - return
    - - {this.visible ? svg_eye_cross : svg_eye} -
    ; - } - - event click $(svg) { - var el = this.$(input); - var value = el.value; - var start = el.xcall(#selectionStart) || 0; - var end = el.xcall(#selectionEnd); - this.update({ visible: !this.visible }); - var me = this; - self.timer(30ms, function() { - var el = me.$(input); - view.focus = el; - el.value = value; - el.xcall(#setSelection, start, end); - }); - } -} - -function isReasonableSize(r) { - var x = r[0]; - var y = r[1]; - return !(x < -3200 || x > 3200 || y < -3200 || y > 3200); -} - diff --git a/src/ui/file_transfer.tis b/src/ui/file_transfer.js similarity index 58% rename from src/ui/file_transfer.tis rename to src/ui/file_transfer.js index 3623eaff5..70d312239 100644 --- a/src/ui/file_transfer.tis +++ b/src/ui/file_transfer.js @@ -1,30 +1,34 @@ +import { handler,svg_send,translate,msgbox } from "./common.js"; +import {$} from "@sciter"; + var remote_home_dir; -var svg_add_folder = +const svg_add_folder = ( -; -var svg_trash = +); +const svg_trash = ( -; -var svg_arrow = +); +export const svg_arrow = ( -; -var svg_home = +); +const svg_home = ( -; -var svg_refresh = +); +const svg_refresh = ( -; -var svg_cancel = ; -var svg_computer = +); +export const svg_cancel = (); +const svg_computer = ( -; +); +// TODO function getSize(type, size) { if (!size) { if (type <= 3) return ""; @@ -47,15 +51,15 @@ function getSize(type, size) { } function getParentPath(is_remote, path) { - var sep = handler.get_path_sep(is_remote); - var res = path.lastIndexOf(sep); + let sep = handler.xcall("get_path_sep",is_remote); + let res = path.lastIndexOf(sep); if (res <= 0) return "/"; return path.substr(0, res); } function getFileName(is_remote, path) { - var sep = handler.get_path_sep(is_remote); - var res = path.lastIndexOf(sep); + let sep = handler.xcall("get_path_sep",is_remote); + let res = path.lastIndexOf(sep); return path.substr(res + 1); } @@ -63,76 +67,75 @@ function getExt(name) { if (name.indexOf(".") == 0) { return ""; } - var i = name.lastIndexOf("."); + let i = name.lastIndexOf("."); if (i > 0) return name.substr(i + 1); return ""; } var jobIdCounter = 1; -class JobTable: Reactor.Component { - this var jobs = []; - this var job_map = {}; +class JobTable extends Element { + jobs = []; + job_map = {}; - function render() { - var me = this; - var rows = this.jobs.map(function(job, i) { return me.renderRow(job, i); }); - return
    + render() { + let rows = this.jobs.map((job, i)=>this.renderRow(job, i)); + return (
    {rows} -
    ; + ); } - event click $(svg.cancel) (_, me) { - var job = this.jobs[me.parent.parent.index]; - var id = job.id; - handler.cancel_job(id); + ["on click at svg.cancel"](_, me) { + let job = this.jobs[me.parentElement.parentElement.index]; + let id = job.id; + handler.xcall("cancel_job",id); delete this.job_map[id]; - var i = -1; + let i = -1; this.jobs.map(function(job, idx) { if (job.id == id) i = idx; }); this.jobs.splice(i, 1); - this.update(); - var is_remote = job.is_remote; + this.componentUpdate(); + let is_remote = job.is_remote; if (job.type != "del-dir") is_remote = !is_remote; refreshDir(is_remote); } - function send(path, is_remote) { - var to; - var show_hidden; + send(path, is_remote) { + let to; + let show_hidden; if (is_remote) { - to = file_transfer.local_folder_view.fd.path; + to = file_transfer.local_folder_view.fd.path; // NULL show_hidden = file_transfer.remote_folder_view.show_hidden; } else { to = file_transfer.remote_folder_view.fd.path; show_hidden = file_transfer.local_folder_view.show_hidden; } if (!to) return; - to += handler.get_path_sep(!is_remote) + getFileName(is_remote, path); - var id = jobIdCounter; + to += handler.xcall("get_path_sep",!is_remote) + getFileName(is_remote, path); + let id = jobIdCounter; jobIdCounter += 1; this.jobs.push({ type: "transfer", id: id, path: path, to: to, include_hidden: show_hidden, is_remote: is_remote }); this.job_map[id] = this.jobs[this.jobs.length - 1]; - handler.send_files(id, path, to, show_hidden, is_remote); - this.update(); + handler.xcall("send_files",id, path, to, show_hidden, is_remote); + this.componentUpdate(); } - function addDelDir(path, is_remote) { - var id = jobIdCounter; + addDelDir(path, is_remote) { + let id = jobIdCounter; jobIdCounter += 1; this.jobs.push({ type: "del-dir", id: id, path: path, is_remote: is_remote }); this.job_map[id] = this.jobs[this.jobs.length - 1]; - handler.remove_dir_all(id, path, is_remote); - this.update(); + handler.xcall("remove_dir_all",id, path, is_remote); + this.componentUpdate(); } - function getSvg(job) { + getSvg(job) { if (job.type == "transfer") { return svg_send; } else if (job.type == "del-dir") { @@ -140,19 +143,19 @@ class JobTable: Reactor.Component { } } - function getStatus(job) { + getStatus(job) { if (!job.entries) return translate("Waiting"); - var i = job.file_num + 1; - var n = job.num_entries || job.entries.length; + let i = job.file_num + 1; + let n = job.num_entries || job.entries.length; if (i > n) i = n; - var res = i + ' / ' + n + " " + translate("files"); + let res = i + ' / ' + n + " " + translate("files"); if (job.total_size > 0) { - var s = getSize(0, job.finished_size); + let s = getSize(0, job.finished_size); if (s) s += " / "; res += ", " + s + getSize(0, job.total_size); } // below has problem if some file skipped - var percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger(); + let percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger(); if (job.finished) percent = '100'; if (percent) res += ", " + percent + "%"; if (job.finished) res = translate("Finished") + " " + res; @@ -160,17 +163,18 @@ class JobTable: Reactor.Component { return res; } - function updateJob(job) { - var el = this.select("div[id=s" + job.id + "]"); + updateJob(job) { + let el = this.$("div#s" + job.id); // TODO TEST + console.log("updateJob el",el); if (el) el.text = this.getStatus(job); } - function updateJobStatus(id, file_num = -1, err = null, speed = null, finished_size = 0) { - var job = this.job_map[id]; + updateJobStatus(id, file_num = -1, err = null, speed = null, finished_size = 0) { + let job = this.job_map[id]; if (!job) return; if (file_num < job.file_num) return; job.file_num = file_num; - var n = job.num_entries || job.entries.length; + let n = job.num_entries || job.entries.length; job.finished = job.file_num >= n - 1 || err == "cancel"; job.finished_size = finished_size; job.speed = speed || 0; @@ -178,151 +182,158 @@ class JobTable: Reactor.Component { if (job.type == "del-dir") { if (job.finished) { if (!err) { - handler.remove_dir(job.id, job.path, job.is_remote); + handler.xcall("remove_dir",job.id, job.path, job.is_remote); refreshDir(job.is_remote); } } else if (!job.no_confirm) { - handler.confirm_delete_files(id, job.file_num + 1); + handler.xcall("confirm_delete_files",id, job.file_num + 1); } } else if (job.finished || file_num == -1) { refreshDir(!job.is_remote); } } - function renderRow(job, i) { - var svg = this.getSvg(job); - return + renderRow(job, i) { + svg = this.getSvg(job); + return ( {svg} -
    -
    {job.path}
    +
    +
    {job.path}
    {this.getStatus(job)}
    {svg_cancel} - ; + ); } } -class FolderView : Reactor.Component { - this var fd = {}; - this var history = []; - this var show_hidden = false; +class FolderView extends Element { + fd = {}; + history = []; + show_hidden = false; + select_dir; - function sep() { - return handler.get_path_sep(this.is_remote); + sep() { + return handler.xcall("get_path_sep",this.is_remote); } - function this(params) { + this(params) { this.is_remote = params.is_remote; if (this.is_remote) { - this.show_hidden = !!handler.get_option("remote_show_hidden"); + this.show_hidden = !!handler.xcall("get_option","remote_show_hidden"); } else { - this.show_hidden = !!handler.get_option("local_show_hidden"); + this.show_hidden = !!handler.xcall("get_option","local_show_hidden"); } if (!this.is_remote) { - var dir = handler.get_option("local_dir"); + let dir = handler.xcall("get_option","local_dir"); if (dir) { - this.fd = handler.read_dir(dir, this.show_hidden); + this.fd = handler.xcall("read_dir",dir, this.show_hidden); if (this.fd) return; } - this.fd = handler.read_dir(handler.get_home_dir(), this.show_hidden); + this.fd = handler.xcall("read_dir",handler.xcall("get_home_dir"), this.show_hidden); } } // sort predicate - function foldersFirst(a, b) { + foldersFirst(a, b) { if (a.type <= 3 && b.type > 3) return -1; if (a.type > 3 && b.type <= 3) return +1; if (a.name == b.name) return 0; - return a.name.toLowerCase().lexicalCompare(b.name.toLowerCase()); + return a.name.toLowerCase().lexicalCompare(b.name.toLowerCase()); // TODO lexicalCompare } - function render() + render() { - return
    + return (
    {this.renderTitle()} {this.renderNavBar()} {this.renderOpBar()} {this.renderTable()} -
    ; +
    ); } - function renderTitle() { - return
    + renderTitle() { + return (
    {svg_computer} -
    {platformSvg(handler.get_platform(this.is_remote), "white")}
    +
    {platformSvg(handler.xcall("get_platform",this.is_remote), "white")}
    {translate(this.is_remote ? "Remote Computer" : "Local Computer")}
    -
    +
    ) } - function renderNavBar() { - return
    -
    {svg_home}
    -
    {svg_arrow}
    -
    {svg_arrow}
    + renderNavBar() { + return ; } - function renderSelect() { - return - ; + ); } - function renderOpBar() { + renderOpBar() { if (this.is_remote) { - return
    -
    {svg_send}{translate('Receive')}
    -
    -
    {svg_add_folder}
    -
    {svg_trash}
    -
    ; + return (
    +
    {svg_send}{translate('Receive')}
    +
    +
    {svg_add_folder}
    +
    {svg_trash}
    +
    ); } - return
    -
    {svg_add_folder}
    -
    {svg_trash}
    -
    -
    {translate('Send')}{svg_send}
    -
    ; + return (
    +
    {svg_add_folder}
    +
    {svg_trash}
    +
    +
    {translate('Send')}{svg_send}
    +
    ); } - function get_updated() { - this.table.sortRows(false); + get_updated() { + this.table.sortRows(false); // TODO sortRows if (this.fd && this.fd.path) this.select_dir.value = this.fd.path; } - function renderTable() { - var fd = this.fd; - var entries = fd.entries || []; - var table = this.table; + renderTable() { + let fd = this.fd; + let entries = fd.entries || []; + let table = this.table; if (!table || !table.sortBy) { - entries.sort(this.foldersFirst); + entries.sort(this.foldersFirst); // TODO sort function } - var me = this; - var path = fd.path; + let path = fd.path; if (path != "/" && path) { entries = [{ name: "..", type: 1 }].concat(entries); } - var rows = entries.map(function(e) { return me.renderRow(e); }); - var id = (this.is_remote ? "remote" : "local") + "-folder-view"; - return + let rows = entries.map(e=>this.renderRow(e)); + let id = (this.is_remote ? "remote" : "local") + "-folder-view"; + //@{} return (
    + + return (
    - + {rows} - -
  • {svg_checkmark}{translate('Show Hidden Files')}
  • + +
  • {svg_checkmark}{translate('Show Hidden Files')}
  • -
    {translate('Name')}{translate('Modified')}{translate('Size')}
    {translate('Name')}{translate('Modified')}{translate('Size')}
    ; + ); } - function joinPath(name) { - var path = this.fd.path; + joinPath(name) { + let path = this.fd.path; if (path == "/") { if (this.sep() == "/") return this.sep() + name; else return name; @@ -330,91 +341,89 @@ class FolderView : Reactor.Component { return path + (path[path.length - 1] == this.sep() ? "" : this.sep()) + name; } - function attached() { - var me = this; - this.table.onRowDoubleClick = function (row) { - var type = row[0].attributes["type"]; + attached() { + this.table.onRowDoubleClick = (row)=>{ + let type = row[0].attributes["type"]; if (type > 3) return; - var name = row[1].text; - var path = name == ".." ? getParentPath(me.is_remote, me.fd.path) : me.joinPath(name); - me.goto(path, true); + let name = row[1].text; + let path = name == ".." ? getParentPath(this.is_remote, this.fd.path) : this.joinPath(name); + this.goto(path, true); } this.get_updated(); } - function goto(path, push) { + goto(path, push) { if (!path) return; if (this.sep() == "\\" && path.length == 2) { // windows drive path += "\\"; } if (push) this.pushHistory(); if (this.is_remote) { - handler.read_remote_dir(path, this.show_hidden); + handler.xcall("read_remote_dir",path, this.show_hidden); } else { - var fd = handler.read_dir(path, this.show_hidden); + var fd = handler.xcall("read_dir",path, this.show_hidden); this.refresh({ fd: fd }); } } - function refresh(data) { + refresh(data) { if (!data.fd || !data.fd.path) return; if (this.is_remote && !remote_home_dir) { remote_home_dir = data.fd.path; } - this.update(data); - var me = this; - self.timer(1ms, function() { me.get_updated(); }); + this.componentUpdate(data); + setTimeout(()=>this.get_updated(),1); } - function renderRow(entry) { - var path; + renderRow(entry) { + let path; if (this.is_remote) { - path = handler.get_icon_path(entry.type, getExt(entry.name)); + path = handler.xcall("get_icon_path",entry.type, getExt(entry.name)); } else { path = this.joinPath(entry.name); } - var tm = entry.time ? new Date(entry.time.toFloat() * 1000.).toLocaleString() : 0; - return + let tm = entry.time ? new Date(entry.time.toFloat() * 1000.).toLocaleString() : 0; // TODO toFloat() + return ( {entry.name} {tm || ""} {getSize(entry.type, entry.size)} - ; + ); } - event click $(#switch-hidden) { + ["on click at #switch-hidden"]() { this.show_hidden = !this.show_hidden; this.refreshDir(); } - event click $(.goup) () { - var path = this.fd.path; + ["on click at .goup"]() { + let path = this.fd.path; if (!path || path == "/") return; path = getParentPath(this.is_remote, path); this.goto(path, true); } - event click $(.goback) () { - var path = this.history.pop(); + ["on click at .goback"] () { + let path = this.history.pop(); if (!path) return; this.goto(path, false); } - event click $(.trash) () { - var rows = this.getCurrentRows(); + ["on click at .trash"]() { + let rows = this.getCurrentRows(); if (!rows || rows.length == 0) return; - var delete_dirs = new Array(); + let delete_dirs = new Array(); - for (var i = 0; i < rows.length; ++i) { - var row = rows[i]; + for (let i = 0; i < rows.length; ++i) { + let row = rows[i]; - var path = row[0]; - var type = row[1]; + let path = row[0]; + let type = row[1]; - var new_history = []; - for (var j = 0; j < this.history.length; ++j) { - var h = this.history[j]; + let new_history = []; + for (let j = 0; j < this.history.length; ++j) { + let h = this.history[j]; if ((h + this.sep()).indexOf(path + this.sep()) == -1) new_history.push(h); } this.history = new_history; @@ -424,97 +433,96 @@ class FolderView : Reactor.Component { confirmDelete(path, this.is_remote); } } - for (var i = 0; i < delete_dirs.length; ++i) { + for (let i = 0; i < delete_dirs.length; ++i) { file_transfer.job_table.addDelDir(delete_dirs[i], this.is_remote); } } - event click $(.add-folder) () { - var me = this; + ["on click at .add-folder"]() { + let me = this; msgbox("custom", translate("Create Folder"), "
    \
    " + translate("Please enter the folder name") + ":
    \
    \
    ", function(res=null) { if (!res) return; if (!res.name) return; - var name = res.name.trim(); + let name = res.name.trim(); if (!name) return; if (name.indexOf(me.sep()) >= 0) { handler.msgbox("custom-error", "Create Folder", "Invalid folder name"); return; } - var path = me.joinPath(name); - handler.create_dir(jobIdCounter, path, me.is_remote); + let path = me.joinPath(name); + handler.xcall("create_dir",jobIdCounter, path, me.is_remote); create_dir_jobs[jobIdCounter] = { is_remote: me.is_remote, path: path }; jobIdCounter += 1; }); } - function refreshDir() { + refreshDir() { this.goto(this.fd.path, false); } - event click $(.refresh) () { + ["on click at .refresh"]() { this.refreshDir(); } - event click $(.home) () { - var path = this.is_remote ? remote_home_dir : handler.get_home_dir(); + ["on click at .home"]() { + let path = this.is_remote ? remote_home_dir : handler.xcall("get_home_dir"); if (!path) return; if (path == this.fd.path) return; this.goto(path, true); } - function getCurrentRow() { - var row = this.table.getCurrentRow(); + getCurrentRow() { + let row = this.table.getCurrentRow(); // TEST getCurrentRow if (!row) return; - var name = row[1].text; + let name = row[1].text; if (!name || name == "..") return; - var type = row[0].attributes["type"]; + let type = row[0].attributes["type"]; return [this.joinPath(name), type]; } - function getCurrentRows() { - var rows = this.table.getCurrentRows(); + getCurrentRows() { + let rows = this.table.getCurrentRows(); if (!rows || rows.length== 0) return; - var records = new Array(); + let records = new Array(); - for (var i = 0; i < rows.length; ++i) { - var name = rows[i][1].text; + for (let i = 0; i < rows.length; ++i) { + let name = rows[i][1].text; if (!name || name == "..") continue; - var type = rows[i][0].attributes["type"]; + let type = rows[i][0].attributes["type"]; records.push([this.joinPath(name), type]); } return records; } - event click $(.send) () { - var rows = this.getCurrentRows(); + ["on click at .send"]() { + let rows = this.getCurrentRows(); if (!rows || rows.length == 0) return; - for (var i = 0; i < rows.length; ++i) { + for (let i = 0; i < rows.length; ++i) { file_transfer.job_table.send(rows[i][0], this.is_remote); } } - event change $(.select-dir) (_, el) { - var x = getTime() - last_key_time; + ["on change at .select-dir"](_, el) { + var x = getTime() - last_key_time; // TODO getTime if (x < 1000) return; if (this.fd.path != el.value) { this.goto(el.value, true); } } - event keydown $(.select-dir) (evt, me) { - if (evt.keyCode == Event.VK_ENTER || - (view.mediaVar("platform") == "OSX" && evt.keyCode == 0x4C)) { + ["on keydown at .select-dir"](evt, me) { + if (evt.code == "KeyRETURN") { // TODO TEST mac this.goto(me.value, true); } } - function pushHistory() { - var path = this.fd.path; + pushHistory() { + let path = this.fd.path; if (!path) return; if (path != this.history[this.history.length - 1]) this.history.push(path); } @@ -522,32 +530,37 @@ class FolderView : Reactor.Component { var file_transfer; -class FileTransfer: Reactor.Component { - function this() { - file_transfer = this; +class FileTransfer extends Element { + this() { + file_transfer = this; } + // TODO @{} + // + // + // - function render() { - return
    - - - -
    ; + render() { + return (
    + + + +
    ); } } -function initializeFileTransfer() +export function initializeFileTransfer() { - $(#file-transfer-wrapper).content(); - $(#video-wrapper).style.set { visibility: "hidden", position: "absolute" }; - $(#file-transfer-wrapper).style.set { display: "block" }; + $("#file-transfer-wrapper").content(); + $("#video-wrapper").style.setProperty("visibility","hidden"); + $("#video-wrapper").style.setProperty("position","absolute"); + $("#file-transfer-wrapper").style.setProperty("display","block"); } handler.updateFolderFiles = function(fd) { fd.entries = fd.entries || []; if (fd.id > 0) { - var jt = file_transfer.job_table; - var job = jt.job_map[fd.id]; + let jt = file_transfer.job_table; + let job = jt.job_map[fd.id]; if (job) { job.file_num = -1; job.total_size = fd.total_size; @@ -565,7 +578,7 @@ handler.jobProgress = function(id, file_num, speed, finished_size) { } handler.jobDone = function(id, file_num = -1) { - var job = deleting_single_file_jobs[id] || create_dir_jobs[id]; + let job = deleting_single_file_jobs[id] || create_dir_jobs[id]; if (job) { refreshDir(job.is_remote); return; @@ -604,7 +617,7 @@ function confirmDelete(path, is_remote) { " + path + "
    \
    ", function(res=null) { if (res) { - handler.remove_file(jobIdCounter, path, 0, is_remote); + handler.xcall("remove_file",jobIdCounter, path, 0, is_remote); deleting_single_file_jobs[jobIdCounter] = { is_remote: is_remote, path: path }; jobIdCounter += 1; } @@ -618,7 +631,7 @@ handler.confirmDeleteFiles = function(id, i, name) { var n = job.num_entries; if (i >= n) return; var file_path = job.path; - if (name) file_path += handler.get_path_sep(job.is_remote) + name; + if (name) file_path += handler.xcall("get_path_sep",job.is_remote) + name; msgbox("custom-skip", "Confirm Delete", "
    \
    " + translate('Deleting') + " #" + (i + 1) + " / " + n + " " + translate('files') + ".
    \
    " + translate('Are you sure you want to delete this file?') + "
    \ @@ -633,18 +646,18 @@ handler.confirmDeleteFiles = function(id, i, name) { } else { job.no_confirm = res.remember; if (job.no_confirm) handler.set_no_confirm(id); - handler.remove_file(id, file_path, i, job.is_remote); + handler.xcall("remove_file",id, file_path, i, job.is_remote); } }); } -function save_file_transfer_close_state() { +export function save_file_transfer_close_state() { var local_dir = file_transfer.local_folder_view.fd.path || ""; var local_show_hidden = file_transfer.local_folder_view.show_hidden ? "Y" : ""; var remote_dir = file_transfer.remote_folder_view.fd.path || ""; var remote_show_hidden = file_transfer.remote_folder_view.show_hidden ? "Y" : ""; - handler.save_close_state("local_dir", local_dir); - handler.save_close_state("local_show_hidden", local_show_hidden); - handler.save_close_state("remote_dir", remote_dir); - handler.save_close_state("remote_show_hidden", remote_show_hidden); + handler.xcall("save_close_state","local_dir", local_dir); + handler.xcall("save_close_state","local_show_hidden", local_show_hidden); + handler.xcall("save_close_state","remote_dir", remote_dir); + handler.xcall("save_close_state","remote_show_hidden", remote_show_hidden); } diff --git a/src/ui/grid.tis b/src/ui/grid.js similarity index 100% rename from src/ui/grid.tis rename to src/ui/grid.js diff --git a/src/ui/header.js b/src/ui/header.js new file mode 100644 index 000000000..c1dcb94a3 --- /dev/null +++ b/src/ui/header.js @@ -0,0 +1,411 @@ +import { handler,view,setWindowButontsAndIcon,translate,msgbox,adjustBorder,is_osx,is_xfce,svg_chat,svg_checkmark, is_linux } from "./common.js"; +import {$,$$} from "@sciter"; +import { adaptDisplay, audio_enabled, clipboard_enabled, keyboard_enabled } from "./remote.js"; +var pi = handler.xcall("get_default_pi"); // peer information + +var chat_msgs = []; + +const svg_fullscreen = ( + +); +const svg_action = (); +const svg_display = ( + +); +const svg_secure = ( + +); +const svg_insecure = (); +const svg_insecure_relay = (); +const svg_secure_relay = (); + +var cur_window_state = view.state; + + +if (is_linux) { + // check_state_change; + setInterval(() => { + if (view.state != cur_window_state) { + stateChanged(); + } + }, 30); +} else { + view.on("statechange",()=>{ + stateChanged(); + }) +} + +function get_id() { + return handler.xcall("get_option","alias") || handler.xcall("get_id") +} + +function stateChanged() { + console.log('state changed from ' + cur_window_state + ' -> ' + view.state); + cur_window_state = view.state; + adjustBorder(); + adaptDisplay(); + if (cur_window_state != Window.WINDOW_MINIMIZED) { + view.focus = handler; // to make focus away from restore/maximize button, so that enter key work + } + let fs = view.state == Window.WINDOW_FULL_SCREEN; + let el = $("#fullscreen"); + if (el) el.classList.toggle("active", fs); + el = $("#maximize"); + if (el) { + el.state.disabled = fs; // TODO TEST + } + if (fs) { + $("header").style.setProperty("display","none"); + } +} + +export var header; +var old_window_state = Window.WINDOW_SHOWN; +var input_blocked; + +class Header extends Element { + this() { + header = this; + } + + render() { + let icon_conn; + let title_conn; + if (this.secure_connection && this.direct_connection) { + icon_conn = svg_secure; + title_conn = translate("Direct and encrypted connection"); + } else if (this.secure_connection && !this.direct_connection) { + icon_conn = svg_secure_relay; + title_conn = translate("Relayed and encrypted connection"); + } else if (!this.secure_connection && this.direct_connection) { + icon_conn = svg_insecure; + title_conn = translate("Direct and unencrypted connection"); + } else { + icon_conn = svg_insecure_relay; + title_conn = translate("Relayed and unencrypted connection"); + } + let title = get_id(); + if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")"; + if ((pi.displays || []).length == 0) { + return (
    {title}
    ); + } + let screens = pi.displays.map(function(d, i) { + return
    + {i+1} +
    ; + }); + updateWindowToolbarPosition(); + let style = "flow:horizontal;"; + if (is_osx) style += "margin:*"; + setTimeout(toggleMenuState,1); + + return (
    + {is_osx || is_xfce ? "" : {svg_fullscreen}} +
    + {icon_conn} +
    {get_id()}
    +
    {screens}
    + {this.renderGlobalScreens()} +
    + {svg_chat} + {svg_action} + {svg_display} + {this.renderDisplayPop()} + {this.renderActionPop()} +
    ); + } + + renderDisplayPop() { + return ( + + + + ); + } + + renderActionPop() { + return ( + +
  • {translate('Transfer File')}
  • +
  • {translate('TCP Tunneling')}
  • +
    + {keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ?
  • {translate('Insert')} Ctrl + Alt + Del
  • : ""} +
    + {keyboard_enabled ?
  • {translate('Insert Lock')}
  • : ""} + {false && pi.platform == "Windows" ?
  • Block user input
  • : ""} + {handler.xcall("support_refresh") ?
  • {translate('Refresh')}
  • : ""} +
    +
    ); + } + + renderGlobalScreens() { + if (pi.displays.length < 3) return ""; + let x0 = 9999999; + let y0 = 9999999; + let x = -9999999; + let y = -9999999; + pi.displays.map(function(d, i) { + if (d.x < x0) x0 = d.x; + if (d.y < y0) y0 = d.y; + let dx = d.x + d.width; + if (dx > x) x = dx; + let dy = d.y + d.height; + if (dy > y) y = dy; + }); + let w = x - x0; + let h = y - y0; + let scale = 16. / h; + let screens = pi.displays.map(function(d, i) { + let min_wh = d.width > d.height ? d.height : d.width; + let fs = min_wh * 0.9 * scale; + let style = "width:" + (d.width * scale) + "px;" + + "height:" + (d.height * scale) + "px;" + + "left:" + ((d.x - x0) * scale) + "px;" + + "top:" + ((d.y - y0) * scale) + "px;" + + "font-size:" + fs + "px;"; + if (is_osx) { + style += "line-height:" + fs + "px;"; + } + return
    {i+1}
    ; + }); + + let style = "width:" + (w * scale) + "px; height:" + (h * scale) + "px;"; + return
    + {screens} +
    ; + } + + ["on click at #fullscreen"](_, el) { + if (view.state == Window.WINDOW_FULL_SCREEN) { + if (old_window_state == Window.WINDOW_MAXIMIZED) { + view.state = Window.WINDOW_SHOWN; + } + view.state = old_window_state; + } else { + old_window_state = view.state; + if (view.state == Window.WINDOW_MAXIMIZED) { + view.state = Window.WINDOW_SHOWN; + } + view.state = Window.WINDOW_FULL_SCREEN; + if (is_linux) { setTimeout(()=>view.state = Window.WINDOW_FULL_SCREEN,150); } + } + } + + ["on click at #chat"]() { + startChat(); + } + + ["on click at #action"](_, me) { + let menu = $("menu#action-options"); + me.popup(menu); + } + + ["on click at #display"](_, me) { + let menu = $("menu#display-options"); + me.popup(menu); + } + + ["on click at #screen"](_, me) { + if (pi.current_display == me.index) return; + handler.xcall("switch_display",me.index); + } + + ["on click at #transfer-file"]() { + handler.xcall("transfer_file"); + } + + ["on click at #tunnel"] () { + handler.xcall("tunnel"); + } + + ["on click at #ctrl-alt-del"]() { + handler.xcall("ctrl_alt_del"); + } + + ["on click at #lock-screen"]() { + handler.xcall("lock_screen"); + } + + ["on click at #refresh"] () { + handler.xcall("refresh_video"); + } + + ["on click at #block-input"] (_,me) { + if (!input_blocked) { + handler.xcall("toggle_option","block-input"); + input_blocked = true; + me.text = "Unblock user input"; // TEST + } else { + handler.xcall("toggle_option","unblock-input"); + input_blocked = false; + me.text = "Block user input"; + } + } + + ["on click at menu#display-options>li"] (_, me) { + if (me.id == "custom") { + handle_custom_image_quality(); + } else if (me.attributes.hasClass("toggle-option")) { + handler.toggle_option(me.id); + toggleMenuState(); + } else if (!me.attributes.hasClass("selected")) { + let type = me.attributes["type"]; + if (type == "image-quality") { + handler.xcall("save_image_quality",me.id); + } else if (type == "view-style") { + handler.xcall("save_view_style",me.id); + adaptDisplay(); + } + toggleMenuState(); + } + } +} + +function handle_custom_image_quality() { + let tmp = handler.xcall("get_custom_image_quality"); + let bitrate0 = tmp[0] || 50; + let quantizer0 = tmp.length > 1 ? tmp[1] : 100; + msgbox("custom", "Custom Image Quality", "
    \ +
    x% bitrate
    \ +
    x% quantizer
    \ +
    ", function(res=null) { + if (!res) return; + if (!res.bitrate) return; + handler.xcall("save_custom_image_quality",res.bitrate, res.quantizer); + toggleMenuState(); + }); +} + +function toggleMenuState() { + let values = []; + let q = handler.xcall("get_image_quality"); + if (!q) q = "balanced"; + values.push(q); + let s = handler.xcall("get_view_style"); + if (!s) s = "original"; + values.push(s); + for (let el of $$("menu#display-options>li")) { + el.classList.toggle("selected", values.indexOf(el.id) >= 0); + } + for (let id of ["show-remote-cursor", "disable-audio", "disable-clipboard", "lock-after-session-end", "privacy-mode"]) { + let el = $('#' + id); // TEST + if (el) { + el.classList.toggle("selected", handler.xcall("get_toggle_option",id)); + } + } +} + +if (is_osx) { + $("header").content(
    ); + $("header").attributes["role"] = "window-caption"; // TODO +} else { + if (handler.is_file_transfer || handler.is_port_forward) { + $("caption").content(
    ); + } else { + $("div.window-toolbar").content(
    ); + } + setWindowButontsAndIcon(); +} + +if (!(handler.is_file_transfer || handler.is_port_forward)) { + $("header").style.setProperty("height","32px"); + if (!is_osx) { + $("div.window-icon").style.setProperty("size","32px"); + } +} + +handler.updatePi = function(v) { + pi = v; + header.componentUpdate(); + if (handler.is_port_forward) { + view.state = Window.WINDOW_MINIMIZED; + } +} + +handler.switchDisplay = function(i) { + pi.current_display = i; + header.componentUpdate(); +} + +function updateWindowToolbarPosition() { + if (is_osx) return; + setTimeout(function() { + let el = $("div.window-toolbar"); + let w1 = el.state.box("width", "border"); // TEST + let w2 = $("header").state.box("width", "border"); + let x = (w2 - w1) / 2; + el.style.setProperty("left",x + "px"); + el.style.setProperty("display","block") + },1); +} + +view.onsizechange = function() { + // ensure size is done, so add timer + setTimeout(function() { + updateWindowToolbarPosition(); + adaptDisplay(); + },1); +}; + +handler.newMessage = function(text) { + chat_msgs.push({text: text, name: pi.username || "", time: getNowStr()}); + startChat(); +} + +function sendMsg(text) { + chat_msgs.push({text: text, name: "me", time: getNowStr()}); + handler.xcall("send_chat",text); + if (chatbox) chatbox.refresh(); +} + +var chatbox; +function startChat() { + if (chatbox) { + chatbox.state = Window.WINDOW_SHOWN; // TODO TEST el.state + chatbox.refresh(); // TODO el.refresh + return; + } + let icon = handler.xcall("get_icon"); + let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw"); // TEST + let w = 300; + let h = 400; + let x = (sx + sw - w) / 2; + let y = sy + 80; + let params = { + type: Window.FRAME_WINDOW, + x: x, + y: y, + width: w, + height: h, + client: true, + parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon }, + caption: get_id(), + }; + let html = handler.xcall("get_chatbox"); + if (html) params.html = html; + else params.url = document.url("chatbox.html"); + chatbox = view.window(params); // TEST +} + +handler.setConnectionType = function(secured, direct) { + // TEST + header.componentUpdate({ + secure_connection: secured, + direct_connection: direct, + }); +} diff --git a/src/ui/header.tis b/src/ui/header.tis deleted file mode 100644 index 0c40df158..000000000 --- a/src/ui/header.tis +++ /dev/null @@ -1,413 +0,0 @@ -var pi = handler.get_default_pi(); // peer information -var chat_msgs = []; - -var svg_fullscreen = - -; -var svg_action = ; -var svg_display = - -; -var svg_secure = - -; -var svg_insecure = ; -var svg_insecure_relay = ; -var svg_secure_relay = ; - -var cur_window_state = view.windowState; -function check_state_change() { - if (view.windowState != cur_window_state) { - stateChanged(); - } - self.timer(30ms, check_state_change); -} - -if (is_linux) { - check_state_change(); -} else { - view << event statechange { - stateChanged(); - } -} - -function get_id() { - return handler.get_option('alias') || handler.get_id() -} - -function stateChanged() { - stdout.println('state changed from ' + cur_window_state + ' -> ' + view.windowState); - cur_window_state = view.windowState; - adjustBorder(); - adaptDisplay(); - if (cur_window_state != View.WINDOW_MINIMIZED) { - view.focus = handler; // to make focus away from restore/maximize button, so that enter key work - } - var fs = view.windowState == View.WINDOW_FULL_SCREEN; - var el = $(#fullscreen); - if (el) el.attributes.toggleClass("active", fs); - el = $(#maximize); - if (el) { - el.state.disabled = fs; - } - if (fs) { - $(header).style.set { - display: "none", - }; - } -} - -var header; -var old_window_state = View.WINDOW_SHOWN; -var input_blocked; - -class Header: Reactor.Component { - function this() { - header = this; - } - - function render() { - var icon_conn; - var title_conn; - if (this.secure_connection && this.direct_connection) { - icon_conn = svg_secure; - title_conn = translate("Direct and encrypted connection"); - } else if (this.secure_connection && !this.direct_connection) { - icon_conn = svg_secure_relay; - title_conn = translate("Relayed and encrypted connection"); - } else if (!this.secure_connection && this.direct_connection) { - icon_conn = svg_insecure; - title_conn = translate("Direct and unencrypted connection"); - } else { - icon_conn = svg_insecure_relay; - title_conn = translate("Relayed and unencrypted connection"); - } - var title = get_id(); - if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")"; - if ((pi.displays || []).length == 0) { - return
    {title}
    ; - } - var screens = pi.displays.map(function(d, i) { - return
    - {i+1} -
    ; - }); - updateWindowToolbarPosition(); - var style = "flow:horizontal;"; - if (is_osx) style += "margin:*"; - self.timer(1ms, toggleMenuState); - return
    - {is_osx || is_xfce ? "" : {svg_fullscreen}} -
    - {icon_conn} -
    {get_id()}
    -
    {screens}
    - {this.renderGlobalScreens()} -
    - {svg_chat} - {svg_action} - {svg_display} - {this.renderDisplayPop()} - {this.renderActionPop()} -
    ; - } - - function renderDisplayPop() { - return - -
  • {translate('Adjust Window')}
  • -
    -
  • {svg_checkmark}{translate('Original')}
  • -
  • {svg_checkmark}{translate('Shrink')}
  • -
  • {svg_checkmark}{translate('Stretch')}
  • -
    -
  • {svg_checkmark}{translate('Good image quality')}
  • -
  • {svg_checkmark}{translate('Balanced')}
  • -
  • {svg_checkmark}{translate('Optimize reaction time')}
  • -
  • {svg_checkmark}{translate('Custom')}
  • -
    -
  • {svg_checkmark}{translate('Show remote cursor')}
  • - {audio_enabled ?
  • {svg_checkmark}{translate('Mute')}
  • : ""} - {keyboard_enabled && clipboard_enabled ?
  • {svg_checkmark}{translate('Disable clipboard')}
  • : ""} - {keyboard_enabled ?
  • {svg_checkmark}{translate('Lock after session end')}
  • : ""} - {false && pi.platform == "Windows" ?
  • {svg_checkmark}{translate('Privacy mode')}
  • : ""} - - ; - } - - function renderActionPop() { - return - -
  • {translate('Transfer File')}
  • -
  • {translate('TCP Tunneling')}
  • -
    - {keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ?
  • {translate('Insert')} Ctrl + Alt + Del
  • : ""} -
    - {keyboard_enabled ?
  • {translate('Insert Lock')}
  • : ""} - {false && pi.platform == "Windows" ?
  • Block user input
  • : ""} - {handler.support_refresh() ?
  • {translate('Refresh')}
  • : ""} - - ; - } - - function renderGlobalScreens() { - if (pi.displays.length < 3) return ""; - var x0 = 9999999; - var y0 = 9999999; - var x = -9999999; - var y = -9999999; - pi.displays.map(function(d, i) { - if (d.x < x0) x0 = d.x; - if (d.y < y0) y0 = d.y; - var dx = d.x + d.width; - if (dx > x) x = dx; - var dy = d.y + d.height; - if (dy > y) y = dy; - }); - var w = x - x0; - var h = y - y0; - var scale = 16. / h; - var screens = pi.displays.map(function(d, i) { - var min_wh = d.width > d.height ? d.height : d.width; - var fs = min_wh * 0.9 * scale; - var style = "width:" + (d.width * scale) + "px;" + - "height:" + (d.height * scale) + "px;" + - "left:" + ((d.x - x0) * scale) + "px;" + - "top:" + ((d.y - y0) * scale) + "px;" + - "font-size:" + fs + "px;"; - if (is_osx) { - style += "line-height:" + fs + "px;"; - } - return
    {i+1}
    ; - }); - - var style = "width:" + (w * scale) + "px; height:" + (h * scale) + "px;"; - return
    - {screens} -
    ; - } - - event click $(#fullscreen) (_, el) { - if (view.windowState == View.WINDOW_FULL_SCREEN) { - if (old_window_state == View.WINDOW_MAXIMIZED) { - view.windowState = View.WINDOW_SHOWN; - } - view.windowState = old_window_state; - } else { - old_window_state = view.windowState; - if (view.windowState == View.WINDOW_MAXIMIZED) { - view.windowState = View.WINDOW_SHOWN; - } - view.windowState = View.WINDOW_FULL_SCREEN; - if (is_linux) { self.timer(150ms, function() { view.windowState = View.WINDOW_FULL_SCREEN; }); } - } - } - - event click $(#chat) { - startChat(); - } - - event click $(#action) (_, me) { - var menu = $(menu#action-options); - me.popup(menu); - } - - event click $(#display) (_, me) { - var menu = $(menu#display-options); - me.popup(menu); - } - - event click $(#screen) (_, me) { - if (pi.current_display == me.index) return; - handler.switch_display(me.index); - } - - event click $(#transfer-file) { - handler.transfer_file(); - } - - event click $(#tunnel) { - handler.tunnel(); - } - - event click $(#ctrl-alt-del) { - handler.ctrl_alt_del(); - } - - event click $(#lock-screen) { - handler.lock_screen(); - } - - event click $(#refresh) { - handler.refresh_video(); - } - - event click $(#block-input) { - if (!input_blocked) { - handler.toggle_option("block-input"); - input_blocked = true; - $(#block-input).text = "Unblock user input"; - } else { - handler.toggle_option("unblock-input"); - input_blocked = false; - $(#block-input).text = "Block user input"; - } - } - - event click $(menu#display-options>li) (_, me) { - if (me.id == "custom") { - handle_custom_image_quality(); - } else if (me.attributes.hasClass("toggle-option")) { - handler.toggle_option(me.id); - toggleMenuState(); - } else if (!me.attributes.hasClass("selected")) { - var type = me.attributes["type"]; - if (type == "image-quality") { - handler.save_image_quality(me.id); - } else if (type == "view-style") { - handler.save_view_style(me.id); - adaptDisplay(); - } - toggleMenuState(); - } - } -} - -function handle_custom_image_quality() { - var tmp = handler.get_custom_image_quality(); - var bitrate0 = tmp[0] || 50; - var quantizer0 = tmp.length > 1 ? tmp[1] : 100; - msgbox("custom", "Custom Image Quality", "
    \ -
    x% bitrate
    \ -
    x% quantizer
    \ -
    ", function(res=null) { - if (!res) return; - if (!res.bitrate) return; - handler.save_custom_image_quality(res.bitrate, res.quantizer); - toggleMenuState(); - }); -} - -function toggleMenuState() { - var values = []; - var q = handler.get_image_quality(); - if (!q) q = "balanced"; - values.push(q); - var s = handler.get_view_style(); - if (!s) s = "original"; - values.push(s); - for (var el in $$(menu#display-options>li)) { - el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); - } - for (var id in ["show-remote-cursor", "disable-audio", "disable-clipboard", "lock-after-session-end", "privacy-mode"]) { - var el = self.select('#' + id); - if (el) { - el.attributes.toggleClass("selected", handler.get_toggle_option(id)); - } - } -} - -if (is_osx) { - $(header).content(
    ); - $(header).attributes["role"] = "window-caption"; -} else { - if (is_file_transfer || is_port_forward) { - $(caption).content(
    ); - } else { - $(div.window-toolbar).content(
    ); - } - setWindowButontsAndIcon(); -} - -if (!(is_file_transfer || is_port_forward)) { - $(header).style.set { - height: "32px", - }; - if (!is_osx) { - $(div.window-icon).style.set { - size: "32px", - }; - } -} - -handler.updatePi = function(v) { - pi = v; - header.update(); - if (is_port_forward) { - view.windowState = View.WINDOW_MINIMIZED; - } -} - -handler.switchDisplay = function(i) { - pi.current_display = i; - header.update(); -} - -function updateWindowToolbarPosition() { - if (is_osx) return; - self.timer(1ms, function() { - var el = $(div.window-toolbar); - var w1 = el.box(#width, #border); - var w2 = $(header).box(#width, #border); - var x = (w2 - w1) / 2; - el.style.set { - left: x + "px", - display: "block", - }; - }); -} - -view.on("size", function() { - // ensure size is done, so add timer - self.timer(1ms, function() { - updateWindowToolbarPosition(); - adaptDisplay(); - }); -}); - -handler.newMessage = function(text) { - chat_msgs.push({text: text, name: pi.username || "", time: getNowStr()}); - startChat(); -} - -function sendMsg(text) { - chat_msgs.push({text: text, name: "me", time: getNowStr()}); - handler.send_chat(text); - if (chatbox) chatbox.refresh(); -} - -var chatbox; -function startChat() { - if (chatbox) { - chatbox.windowState = View.WINDOW_SHOWN; - chatbox.refresh(); - return; - } - var icon = handler.get_icon(); - var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw); - var w = 300; - var h = 400; - var x = (sx + sw - w) / 2; - var y = sy + 80; - var params = { - type: View.FRAME_WINDOW, - x: x, - y: y, - width: w, - height: h, - client: true, - parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon }, - caption: get_id(), - }; - var html = handler.get_chatbox(); - if (html) params.html = html; - else params.url = self.url("chatbox.html"); - chatbox = view.window(params); -} - -handler.setConnectionType = function(secured, direct) { - header.update({ - secure_connection: secured, - direct_connection: direct, - }); -} diff --git a/src/ui/index.html b/src/ui/index.html index fc24b564a..dae2bd221 100644 --- a/src/ui/index.html +++ b/src/ui/index.html @@ -1,15 +1,14 @@ - - + diff --git a/src/ui/index.js b/src/ui/index.js new file mode 100644 index 000000000..80267aa62 --- /dev/null +++ b/src/ui/index.js @@ -0,0 +1,730 @@ +import { is_osx,view,OS,handler,translate,msgbox,is_win,svg_checkmark,svg_edit,isReasonableSize,centerize,svg_eye, PasswordComponent } from "./common"; +import { SearchBar,SessionStyle,SessionList, MultipleSessions } from "./ab.js"; +import {$} from "@sciter"; //TEST $$ import + +if (is_osx) view.blurBehind = "light"; +console.log("current platform:", OS); +console.log("wayland",handler.xcall("is_login_wayland")); +// html min-width, min-height not working on mac, below works for all +view.minSize = [500, 300]; // TODO not work on ubuntu + +export var app; // 注意判空 +var tmp = handler.xcall("get_connect_status"); +var connect_status = tmp[0]; +var service_stopped = false; +var software_update_url = ""; +var key_confirmed = tmp[1]; +var system_error = ""; + +export const svg_menu = + + + +; + +var my_id = ""; +function get_id() { + my_id = handler.xcall("get_id"); + return my_id; +} + +class ConnectStatus extends Element { + render() { + return(
    + + {this.getConnectStatusStr()} + {service_stopped ? {translate('Start Service')} : ""} +
    ); + } + + getConnectStatusStr() { + if (service_stopped) { + return translate("Service is not running"); + } else if (connect_status == -1) { + return translate('not_ready_status'); + } else if (connect_status == 0) { + return translate('connecting_status'); + } + return translate("Ready"); + } + + ["on click at #start-service"]() { + handler.xcall("set_option","stop-service", ""); + } +} + +export function createNewConnect(id, type) { + id = id.replace(/\s/g, ""); + app.remote_id.value = formatId(id); + if (!id) return; + if (id == my_id) { + msgbox("custom-error", "Error", "You cannot connect to your own computer"); + return; + } + handler.xcall("set_remote_id",id); + handler.xcall("new_remote",id, type); +} + +var direct_server; +class DirectServer extends Element { + this() { + direct_server = this; + } + + render() { + var text = translate("Enable Direct IP Access"); + var cls = handler.xcall("get_option", "direct-server") == "Y" ? "selected" : "line-through"; + return
  • {svg_checkmark}{text}
  • ; + } + + onClick() { + handler.xcall("set_option", "direct-server", handler.xcall("get_option", "direct-server") == "Y" ? "" : "Y"); + this.componentUpdate(); + } +} + +var myIdMenu; +var audioInputMenu; +class AudioInputs extends Element { + this() { + audioInputMenu = this; + } + + render() { + // TODO this.show + if (!this.show) return
  • ; + let inputs = handler.xcall("get_sound_inputs"); + if (is_win) inputs = ["System Sound"].concat(inputs); + if (!inputs.length) return
    ; + inputs = ["Mute"].concat(inputs); + setTimeout(()=>this.toggleMenuState(),1); + return (
  • {translate('Audio Input')} + + {inputs.map((name)=>
  • {svg_checkmark}{translate(name)}
  • )} +
    +
  • ); + } + + get_default() { + if (is_win) return "System Sound"; + return ""; + } + + get_value() { + return handler.xcall("get_option","audio-input") || this.get_default(); + } + + toggleMenuState() { + let v = this.get_value(); + for (let el of this.$$("menu#audio-input>li")) { + let selected = el.id == v; + el.classList.toggle("selected", selected); + } + } + + ["on click at menu#audio-input>li"](_, me) { + let v = me.id; + if (v == this.get_value()) return; + if (v == this.get_default()) v = ""; + handler.xcall("set_option","audio-input", v); + this.toggleMenuState(); + } +} + +class MyIdMenu extends Element { + this() { + myIdMenu = this; + } + + render() { + return (
    + {this.renderPop()} + ID{svg_menu} +
    ); + } + + renderPop() { + return ( + +
  • {svg_checkmark}{translate('Enable Keyboard/Mouse')}
  • +
  • {svg_checkmark}{translate('Enable Clipboard')}
  • +
  • {svg_checkmark}{translate('Enable File Transfer')}
  • +
  • {svg_checkmark}{translate('Enable TCP Tunneling')}
  • + +
    +
  • {translate('IP Whitelisting')}
  • +
  • {translate('ID/Relay Server')}
  • +
  • {translate('Socks5 Proxy')}
  • +
    +
  • {svg_checkmark}{translate("Enable Service")}
  • + +
    +
  • {translate('About')} {" "} {handler.xcall("get_app_name")}
  • +
    +
    ); + } + + + ["on click at svg#menu"](_, me) { + + audioInputMenu.componentUpdate({ show: true }); + this.toggleMenuState(); + let menu = this.$("menu#config-options"); + me.popup(menu); + } + + toggleMenuState() { + for (let el of this.$$("menu#config-options>li")) { + if (el.id && el.id.indexOf("enable-") == 0) { + let enabled = handler.xcall("get_option",el.id) != "N"; + console.log(el.id,enabled) + el.classList.toggle("selected", enabled); + el.classList.toggle("line-through", !enabled); + } + } + } + + ["on click at menu#config-options>li"] (_, me) { + if (me.id && me.id.indexOf("enable-") == 0) { + handler.xcall("set_option",me.id, handler.xcall("get_option",me.id) == "N" ? "" : "N"); + } + if (me.id == "whitelist") { + let old_value = handler.xcall("get_option","whitelist").split(",").join("\n"); + msgbox("custom-whitelist", translate("IP Whitelisting"), "
    \ +
    " + translate("whitelist_sep") + "
    \ + \ +
    \ + ", + function(res=null) { + if (!res) return; + let value = (res.text || "").trim(); + if (value) { + let values = value.split(/[\s,;\n]+/g); + for (let ip in values) { + if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) { + return translate("Invalid IP") + ": " + ip; + } + } + value = values.join("\n"); + } + if (value == old_value) return; + console.log("whitelist updated"); + handler.xcall("set_option","whitelist", value.replace("\n", ",")); + }, 300); + } else if (me.id == "custom-server") { + let configOptions = handler.xcall("get_options"); + let old_relay = configOptions["relay-server"] || ""; + let old_id = configOptions["custom-rendezvous-server"] || ""; + msgbox("custom-server", "ID/Relay Server", "
    \ +
    " + translate("ID Server") + ":
    \ +
    " + translate("Relay Server") + ":
    \ +
    \ + ", + function(res=null) { + if (!res) return; + let id = (res.id || "").trim(); + let relay = (res.relay || "").trim(); + if (id == old_id && relay == old_relay) return; + if (id) { + let err = handler.xcall("test_if_valid_server",id); + if (err) return translate("ID Server") + ": " + err; + } + if (relay) { + let err = handler.xcall("test_if_valid_server",relay); + if (err) return translate("Relay Server") + ": " + err; + } + configOptions["custom-rendezvous-server"] = id; + configOptions["relay-server"] = relay; + handler.xcall("set_options",configOptions); + }, 240); + } else if (me.id == "socks5-server") { + var socks5 = handler.xcall("get_socks") || {}; + var old_proxy = socks5[0] || ""; + var old_username = socks5[1] || ""; + var old_password = socks5[2] || ""; + msgbox("custom-server", "Socks5 Proxy",
    +
    {translate("Hostname")}
    +
    {translate("Username")}
    +
    {translate("Password")}
    +
    + , function(res=null) { + if (!res) return; + var proxy = (res.proxy || "").trim(); + var username = (res.username || "").trim(); + var password = (res.password || "").trim(); + if (proxy == old_proxy && username == old_username && password == old_password) return; + if (proxy) { + var err = handler.xcall("test_if_valid_server", proxy); + if (err) return translate("Server") + ": " + err; + } + handler.xcall("set_socks", proxy, username, password); + }, 240); + } else if (me.id == "stop-service") { + handler.xcall("set_option","stop-service", service_stopped ? "" : "Y"); + } else if (me.id == "about") { + let name = handler.xcall("get_app_name"); + msgbox("custom-nocancel-nook-hasclose", "About " + name, "
    \ +
    Version: " + handler.xcall("get_version") + " \ + \ + \ +
    Copyright © 2020 CarrieZ Studio \ +
    Author: Carrie \ +

    Made with heart in this chaotic world!

    \ +
    \ +
    ", + function(el) { + if (el && el.attributes) { + handler.xcall("open_url",el.attributes['url']); + }; + }, 400); + } + } +} + +class App extends Element{ + remote_id; + recent_sessions; + connect_status; + this() { + app = this; + } + + componentDidMount(){ + this.remote_id = this.$("#ID"); + this.multipleSessions = this.$("#multipleSessions"); + this.connect_status = this.$("#ConnectStatus"); + } + + render() { + let is_can_screen_recording = handler.xcall("is_can_screen_recording",false); + return(
    + + +
  • {translate('Refresh random password')}
  • +
  • {translate('Set your own password')}
  • +
    +
    +
    +
    +
    {translate('Your Desktop')}
    +
    {translate('desk_tip')}
    +
    + + {key_confirmed ? : translate("Generating ...")} +
    +
    +
    {translate('Password')}
    + +
    +
    + {handler.xcall("is_installed") ? "": } + {handler.xcall("is_installed") && software_update_url ? : ""} + {handler.xcall("is_installed") && !software_update_url && handler.xcall("is_installed_lower_version") ? : ""} + {is_can_screen_recording ? "": } + {is_can_screen_recording && !handler.xcall("is_process_trusted",false) ? : ""} + {system_error ? : ""} + {!system_error && handler.xcall("is_login_wayland") && !handler.xcall("current_is_wayland") ? : ""} + {!system_error && handler.xcall("current_is_wayland") ? : ""} +
    +
    +
    +
    +
    {translate('Control Remote Desktop')}
    + +
    + + +
    +
    + +
    + +
    +
    ); + } + + ["on click at button#connect"](){ + this.newRemote("connect"); + } + + ["on click at button#file-transfer"]() { + this.newRemote("file-transfer"); + } + + ["on keydown"](evt) { + if (!evt.shortcutKey) { + // TODO TEST Windows/Mac + if (evt.code == "KeyRETURN") { + var el = $("button#connect"); + view.focus = el; + el.click(); + // simulate button click effect, windows does not have this issue + el.classList.toggle("active", true); + el.timer(300, ()=> el.classList.toggle("active", false)); + } + } + } + + newRemote(type) { + createNewConnect(this.remote_id.value, type); + } +} + +class InstallMe extends Element { + render() { + return (
    + +
    {translate('install_tip')}
    +
    +
    ); + } + + ["on click at #install-me"]() { + handler.xcall("goto_install"); + } +} + +const http = function() { + function makeRequest(httpverb) { + return function( params ) { + params.type = httpverb; + // TODO request + view.request(params); + }; + } + function download(from, to, ...args) { + // TODO #get + let rqp = { type:"get", url: from, toFile: to }; + let fn = 0; + let on = 0; + // TODO p in / p of? + for( let p in args ) + if( p instanceof Function ) + { + switch(++fn) { + case 1: rqp.success = p; break; + case 2: rqp.error = p; break; + case 3: rqp.progress = p; break; + } + } else if( p instanceof Object ) + { + switch(++on) { + case 1: rqp.params = p; break; + case 2: rqp.headers = p; break; + } + } + // TODO request + view.request(rqp); + } + + return { + get: makeRequest("get"), + post: makeRequest("post"), + put: makeRequest("put"), + del: makeRequest("delete"), + download: download + }; + +}(); + +class UpgradeMe extends Element { + render() { + let update_or_download = is_osx ? "download" : "update"; + return (
    +
    {translate('Status')}
    +
    {translate('Your installation is lower version.')}
    + +
    ); + } + + ["on click at #install-me"]() { + handler.xcall("update_me"); + } +} + +class UpdateMe extends Element { + render() { + let update_or_download = "download"; // !is_win ? "download" : "update"; + return (
    +
    {translate('Status')}
    +
    There is a newer version of {handler.xcall("get_app_name")} ({handler.xcall("get_new_version")}) available.
    + +
    +
    ); + } + + ["on click at #install-me"]() { + handler.xcall("open_url","https://rustdesk.com"); + return; + if (!is_win) { + handler.xcall("open_url","https://rustdesk.com"); + return; + } + let url = software_update_url + '.' + handler.xcall("get_software_ext"); + let path = handler.xcall("get_software_store_path"); + let onsuccess = function(md5) { + this.$("#download-percent").content(translate("Installing ...")); + handler.xcall("update_me",path); + }; + let onerror = function(err) { + msgbox("custom-error", "Download Error", "Failed to download"); + }; + let onprogress = function(loaded, total) { + if (!total) total = 5 * 1024 * 1024; + let el = this.$("#download-percent"); + el.style.setProperty("display","block"); + el.content("Downloading %" + (loaded * 100 / total)); + }; + console.log("Downloading " + url + " to " + path); + http.download( + url, + document.url(path), + onsuccess, onerror, onprogress); + } +} + +class SystemError extends Element { + render() { + return (
    +
    {system_error}
    +
    ); + } +} + +class TrustMe extends Element { + render() { + return (
    +
    {translate('Configuration Permissions')}
    +
    {translate('config_acc')}
    + +
    ); + } + + ["on click at #trust-me"] () { + handler.xcall("is_process_trusted",true); + watch_trust(); + } +} + +class CanScreenRecording extends Element { + render() { + return (
    +
    {translate('Configuration Permissions')}
    +
    {translate('config_screen')}
    + +
    ); + } + + ["on click at #screen-recording"]() { + handler.xcall("is_can_screen_recording",true); + watch_trust(); + } +} + +class FixWayland extends Element { + render() { + return (
    +
    {translate('Warning')}
    +
    {translate('Login screen using Wayland is not supported')}
    + +
    ({translate('Reboot required')})
    +
    ); + } + + ["on click at #fix-wayland"] () { + handler.xcall("fix_login_wayland"); + app.componentUpdate(); + } +} + +class ModifyDefaultLogin extends Element { + render() { + return (
    +
    {translate('Warning')}
    +
    {translate('Current Wayland display server is not supported')}
    + +
    ({translate('Reboot required')})
    +
    ); + } + + ["on click at #modify-default-login"]() { + let r = handler.xcall("modify_default_login"); + if (r) { + msgbox("custom-error", "Error", r); + } + app.componentUpdate(); + } +} + +function watch_trust() { + // not use TrustMe::update, because it is buggy + let trusted = handler.xcall("is_process_trusted",false); + let el = $("div.trust-me"); + if (el) { + el.style.setProperty("display", trusted ? "none" : "block"); + } + // if (trusted) return; + // TODO dont have exit? + setTimeout(() => { + watch_trust() + }, 1000); +} + +class PasswordEyeArea extends Element { + render() { + return (
    + + {svg_eye} +
    ); + } + + ["on mouseenter"]() { + this.leaved = false; + setTimeout(()=> { + if (this.leaved) return; + this.$("input").value = handler.xcall("get_password"); + },300); + } + + ["on mouseleave"]() { + this.leaved = true; + this.$("input").value = "******"; + } +} + +class Password extends Element { + render() { + return (
    + + {svg_edit} +
    ); + } + + ["on click at svg#edit"](_,me) { + let menu = $("menu#edit-password-context"); + me.popup(menu); + } + + ["on click at li#refresh-password"] () { + handler.xcall("update_password"); + this.componentUpdate(); + } + + ["on click at li#set-password"] () { + // option .form .set-password ... + msgbox("custom-password", translate("Set Password"), "
    \ +
    " + translate('Password') + ":
    \ +
    " + translate('Confirmation') + ":
    \ +
    \ + ", + function(res=null) { + if (!res) return; + let p0 = (res.password || "").trim(); + let p1 = (res.confirmation || "").trim(); + if (p0.length < 6) { + return translate("Too short, at least 6 characters."); + } + if (p0 != p1) { + return translate("The confirmation is not identical."); + } + handler.xcall("update_password",p0); + this.componentUpdate(); + }); + } +} + +class ID extends Element { + render() { + return ; + } + + // TEST + // https://github.com/c-smile/sciter-sdk/blob/master/doc/content/sciter/Event.htm + ["on change"]() { + let fid = formatId(this.value); + let d = this.value.length - (this.old_value || "").length; + this.old_value = this.value; + let start = this.xcall("selectionStart") || 0; + let end = this.xcall("selectionEnd"); + if (fid == this.value || d <= 0 || start != end) { + return; + } + // fix Caret position + this.value = fid; + let text_after_caret = this.old_value.substr(start); + let n = fid.length - formatId(text_after_caret).length; + this.xcall("setSelection", n, n); + } +} + +var reg = /^\d+$/; +export function formatId(id) { + id = id.replace(/\s/g, ""); + if (reg.test(id) && id.length > 3) { + let n = id.length; + let a = n % 3 || 3; + let new_id = id.substr(0, a); + for (let i = a; i < n; i += 3) { + new_id += " " + id.substr(i, 3); + } + return new_id; + } + return id; +} + +document.body.content(); + +document.on("ready",()=>{ + let r = handler.xcall("get_size"); + if (isReasonableSize(r) && r[2] > 0) { + view.move(r[0], r[1], r[2], r[3]); + } else { + centerize(800, 600); + } + if (!handler.xcall("get_remote_id")) { + view.focus = $("#remote_id"); // TEST + } +}) + +document.on("unloadequest",(evt)=>{ + // evt.preventDefault() // can prevent window close + let [x, y, w, h] = view.box("rectw", "border", "desktop"); + handler.xcall("save_size",x, y, w, h); +}) + +// check connect status +setInterval(() => { + let tmp = !!handler.xcall("get_option","stop-service"); + if (tmp != service_stopped) { + service_stopped = tmp; + app.connect_status.componentUpdate(); + myIdMenu.componentUpdate(); + } + tmp = handler.xcall("get_connect_status"); + if (tmp[0] != connect_status) { + connect_status = tmp[0]; + app.connect_status.componentUpdate(); + } + if (tmp[1] != key_confirmed) { + key_confirmed = tmp[1]; + app.componentUpdate(); + } + if (tmp[2] && tmp[2] != my_id) { + console.log("id updated"); + app.componentUpdate(); + } + tmp = handler.xcall("get_error"); + if (system_error != tmp) { + system_error = tmp; + app.componentUpdate(); + } + tmp = handler.xcall("get_software_update_url"); + if (tmp != software_update_url) { + software_update_url = tmp; + app.componentUpdate(); + } + if (handler.xcall("recent_sessions_updated")) { + console.log("recent sessions updated"); + app.componentUpdate(); + } +}, 1000); diff --git a/src/ui/index.tis b/src/ui/index.tis deleted file mode 100644 index a69e5b014..000000000 --- a/src/ui/index.tis +++ /dev/null @@ -1,720 +0,0 @@ -if (is_osx) view.windowBlurbehind = #light; -stdout.println("current platform:", OS); - -// html min-width, min-height not working on mac, below works for all -view.windowMinSize = (500, 300); - -var app; -var tmp = handler.get_connect_status(); -var connect_status = tmp[0]; -var service_stopped = false; -var software_update_url = ""; -var key_confirmed = tmp[1]; -var system_error = ""; - -var svg_menu = - - - -; - -var my_id = ""; -function get_id() { - my_id = handler.get_id(); - return my_id; -} - -class ConnectStatus: Reactor.Component { - function render() { - return -
    - - {this.getConnectStatusStr()} - {service_stopped ? {translate('Start Service')} : ""} -
    ; - } - - function getConnectStatusStr() { - if (service_stopped) { - return translate("Service is not running"); - } else if (connect_status == -1) { - return translate('not_ready_status'); - } else if (connect_status == 0) { - return translate('connecting_status'); - } - return translate("Ready"); - } - - event click $(#start-service) () { - handler.set_option("stop-service", ""); - } -} - -function createNewConnect(id, type) { - id = id.replace(/\s/g, ""); - app.remote_id.value = formatId(id); - if (!id) return; - if (id == my_id) { - msgbox("custom-error", "Error", "You cannot connect to your own computer"); - return; - } - handler.set_remote_id(id); - handler.new_remote(id, type); -} - -var direct_server; -class DirectServer: Reactor.Component { - function this() { - direct_server = this; - } - - function render() { - var text = translate("Enable Direct IP Access"); - var cls = handler.get_option("direct-server") == "Y" ? "selected" : "line-through"; - return
  • {svg_checkmark}{text}
  • ; - } - - function onClick() { - handler.set_option("direct-server", handler.get_option("direct-server") == "Y" ? "" : "Y"); - this.update(); - } -} - -var myIdMenu; -var audioInputMenu; -class AudioInputs: Reactor.Component { - function this() { - audioInputMenu = this; - } - - function render() { - if (!this.show) return
  • ; - var inputs = handler.get_sound_inputs(); - if (is_win) inputs = ["System Sound"].concat(inputs); - if (!inputs.length) return
  • ; - inputs = ["Mute"].concat(inputs); - var me = this; - self.timer(1ms, function() { me.toggleMenuState() }); - return
  • {translate('Audio Input')} - - {inputs.map(function(name) { - return
  • {svg_checkmark}{translate(name)}
  • ; - })} -
    -
  • ; - } - - function get_default() { - if (is_win) return "System Sound"; - return ""; - } - - function get_value() { - return handler.get_option("audio-input") || this.get_default(); - } - - function toggleMenuState() { - var v = this.get_value(); - for (var el in $$(menu#audio-input>li)) { - var selected = el.id == v; - el.attributes.toggleClass("selected", selected); - } - } - - event click $(menu#audio-input>li) (_, me) { - var v = me.id; - if (v == this.get_value()) return; - if (v == this.get_default()) v = ""; - handler.set_option("audio-input", v); - this.toggleMenuState(); - } -} - -class MyIdMenu: Reactor.Component { - function this() { - myIdMenu = this; - } - - function render() { - return
    - {this.renderPop()} - ID{svg_menu} -
    ; - } - - function renderPop() { - return - -
  • {svg_checkmark}{translate('Enable Keyboard/Mouse')}
  • -
  • {svg_checkmark}{translate('Enable Clipboard')}
  • -
  • {svg_checkmark}{translate('Enable File Transfer')}
  • -
  • {svg_checkmark}{translate('Enable TCP Tunneling')}
  • - -
    -
  • {translate('IP Whitelisting')}
  • -
  • {translate('ID/Relay Server')}
  • -
  • {translate('Socks5 Proxy')}
  • -
    -
  • {svg_checkmark}{translate("Enable Service")}
  • - -
    -
  • {translate('About')} {" "} {handler.get_app_name()}
  • - - ; - } - - event click $(svg#menu) (_, me) { - audioInputMenu.update({ show: true }); - this.toggleMenuState(); - if (direct_server) direct_server.update(); - var menu = $(menu#config-options); - me.popup(menu); - } - - function toggleMenuState() { - for (var el in $$(menu#config-options>li)) { - if (el.id && el.id.indexOf("enable-") == 0) { - var enabled = handler.get_option(el.id) != "N"; - el.attributes.toggleClass("selected", enabled); - el.attributes.toggleClass("line-through", !enabled); - } - } - } - - event click $(menu#config-options>li) (_, me) { - if (me.id && me.id.indexOf("enable-") == 0) { - handler.set_option(me.id, handler.get_option(me.id) == "N" ? "" : "N"); - } - if (me.id == "whitelist") { - var old_value = handler.get_option("whitelist").split(",").join("\n"); - msgbox("custom-whitelist", translate("IP Whitelisting"), "
    \ -
    " + translate("whitelist_sep") + "
    \ - \ -
    \ - ", function(res=null) { - if (!res) return; - var value = (res.text || "").trim(); - if (value) { - var values = value.split(/[\s,;\n]+/g); - for (var ip in values) { - if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) { - return translate("Invalid IP") + ": " + ip; - } - } - value = values.join("\n"); - } - if (value == old_value) return; - stdout.println("whitelist updated"); - handler.set_option("whitelist", value.replace("\n", ",")); - }, 300); - } else if (me.id == "custom-server") { - var configOptions = handler.get_options(); - var old_relay = configOptions["relay-server"] || ""; - var old_id = configOptions["custom-rendezvous-server"] || ""; - msgbox("custom-server", "ID/Relay Server", "
    \ -
    " + translate("ID Server") + ":
    \ -
    " + translate("Relay Server") + ":
    \ -
    \ - ", function(res=null) { - if (!res) return; - var id = (res.id || "").trim(); - var relay = (res.relay || "").trim(); - if (id == old_id && relay == old_relay) return; - if (id) { - var err = handler.test_if_valid_server(id); - if (err) return translate("ID Server") + ": " + err; - } - if (relay) { - var err = handler.test_if_valid_server(relay); - if (err) return translate("Relay Server") + ": " + err; - } - configOptions["custom-rendezvous-server"] = id; - configOptions["relay-server"] = relay; - handler.set_options(configOptions); - }, 240); - } else if (me.id == "socks5-server") { - var socks5 = handler.get_socks() || {}; - var old_proxy = socks5[0] || ""; - var old_username = socks5[1] || ""; - var old_password = socks5[2] || ""; - msgbox("custom-server", "Socks5 Proxy",
    -
    {translate("Hostname")}
    -
    {translate("Username")}
    -
    {translate("Password")}
    -
    - , function(res=null) { - if (!res) return; - var proxy = (res.proxy || "").trim(); - var username = (res.username || "").trim(); - var password = (res.password || "").trim(); - if (proxy == old_proxy && username == old_username && password == old_password) return; - if (proxy) { - var err = handler.test_if_valid_server(proxy); - if (err) return translate("Server") + ": " + err; - } - handler.set_socks(proxy, username, password); - }, 240); - } else if (me.id == "stop-service") { - handler.set_option("stop-service", service_stopped ? "" : "Y"); - } else if (me.id == "about") { - var name = handler.get_app_name(); - msgbox("custom-nocancel-nook-hasclose", "About " + name, "
    \ -
    Version: " + handler.get_version() + " \ -
    Privacy Statement
    \ -
    Website
    \ -
    Copyright © 2020 CarrieZ Studio \ -
    Author: Carrie \ -

    Made with heart in this chaotic world!

    \ -
    \ -
    ", function(el) { - if (el && el.attributes) { - handler.open_url(el.attributes['url']); - }; - }, 400); - } - } -} - -class App: Reactor.Component -{ - function this() { - app = this; - } - - function render() { - var is_can_screen_recording = handler.is_can_screen_recording(false); - return -
    - -
  • {translate('Refresh random password')}
  • -
  • {translate('Set your own password')}
  • -
    -
    -
    -
    {translate('Your Desktop')}
    -
    {translate('desk_tip')}
    -
    - - {key_confirmed ? : translate("Generating ...")} -
    -
    -
    {translate('Password')}
    - -
    -
    - {handler.is_installed() ? "": } - {handler.is_installed() && software_update_url ? : ""} - {handler.is_installed() && !software_update_url && handler.is_installed_lower_version() ? : ""} - {is_can_screen_recording ? "": } - {is_can_screen_recording && !handler.is_process_trusted(false) ? : ""} - {system_error ? : ""} - {!system_error && handler.is_login_wayland() && !handler.current_is_wayland() ? : ""} - {!system_error && handler.current_is_wayland() ? : ""} -
    -
    -
    -
    -
    {translate('Control Remote Desktop')}
    - -
    - - -
    -
    - -
    - -
    -
    ; - } - - event click $(button#connect) { - this.newRemote("connect"); - } - - event click $(button#file-transfer) { - this.newRemote("file-transfer"); - } - - function newRemote(type) { - createNewConnect(this.remote_id.value, type); - } -} - -class InstallMe: Reactor.Component { - function render() { - return
    - -
    {translate('install_tip')}
    -
    -
    ; - } - - event click $(#install-me) { - handler.goto_install(); - } -} - -const http = function() { - - function makeRequest(httpverb) { - return function( params ) { - params.type = httpverb; - view.request(params); - }; - } - - function download(from, to, args..) - { - var rqp = { type:#get, url: from, toFile: to }; - var fn = 0; - var on = 0; - for( var p in args ) - if( p instanceof Function ) - { - switch(++fn) { - case 1: rqp.success = p; break; - case 2: rqp.error = p; break; - case 3: rqp.progress = p; break; - } - } else if( p instanceof Object ) - { - switch(++on) { - case 1: rqp.params = p; break; - case 2: rqp.headers = p; break; - } - } - view.request(rqp); - } - - return { - get: makeRequest(#get), - post: makeRequest(#post), - put: makeRequest(#put), - del: makeRequest(#delete), - download: download - }; - -}(); - -class UpgradeMe: Reactor.Component { - function render() { - var update_or_download = is_osx ? "download" : "update"; - return
    -
    {translate('Status')}
    -
    {translate('Your installation is lower version.')}
    -
    {translate('Click to upgrade')}
    -
    ; - } - - event click $(#install-me) { - handler.update_me(""); - } -} - -class UpdateMe: Reactor.Component { - function render() { - var update_or_download = "download"; // !is_win ? "download" : "update"; - return
    -
    {translate('Status')}
    -
    There is a newer version of {handler.get_app_name()} ({handler.get_new_version()}) available.
    -
    Click to {update_or_download}
    -
    -
    ; - } - - event click $(#install-me) { - handler.open_url("https://rustdesk.com"); - return; - if (!is_win) { - handler.open_url("https://rustdesk.com"); - return; - } - var url = software_update_url + '.' + handler.get_software_ext(); - var path = handler.get_software_store_path(); - var onsuccess = function(md5) { - $(#download-percent).content(translate("Installing ...")); - handler.update_me(path); - }; - var onerror = function(err) { - msgbox("custom-error", "Download Error", "Failed to download"); - }; - var onprogress = function(loaded, total) { - if (!total) total = 5 * 1024 * 1024; - var el = $(#download-percent); - el.style.set{display: "block"}; - el.content("Downloading %" + (loaded * 100 / total)); - }; - stdout.println("Downloading " + url + " to " + path); - http.download( - url, - self.url(path), - onsuccess, onerror, onprogress); - } -} - -class SystemError: Reactor.Component { - function render() { - return
    -
    {system_error}
    -
    ; - } -} - -class TrustMe: Reactor.Component { - function render() { - return
    -
    {translate('Configuration Permissions')}
    -
    {translate('config_acc')}
    -
    {translate('Configure')}
    -
    ; - } - - event click $(#trust-me) { - handler.is_process_trusted(true); - watch_trust(); - } -} - -class CanScreenRecording: Reactor.Component { - function render() { - return
    -
    {translate('Configuration Permissions')}
    -
    {translate('config_screen')}
    -
    {translate('Configure')}
    -
    ; - } - - event click $(#screen-recording) { - handler.is_can_screen_recording(true); - watch_trust(); - } -} - -class FixWayland: Reactor.Component { - function render() { - return
    -
    {translate('Warning')}
    -
    {translate('Login screen using Wayland is not supported')}
    -
    {translate('Fix it')}
    -
    ({translate('Reboot required')})
    -
    ; - } - - event click $(#fix-wayland) { - handler.fix_login_wayland(); - app.update(); - } -} - -class ModifyDefaultLogin: Reactor.Component { - function render() { - return
    -
    {translate('Warning')}
    -
    {translate('Current Wayland display server is not supported')}
    -
    {translate('Fix it')}
    -
    ({translate('Reboot required')})
    -
    ; - } - - event click $(#modify-default-login) { - if (var r = handler.modify_default_login()) { - msgbox("custom-error", "Error", r); - } - app.update(); - } -} - -function watch_trust() { - // not use TrustMe::update, because it is buggy - var trusted = handler.is_process_trusted(false); - var el = $(div.trust-me); - if (el) { - el.style.set { - display: trusted ? "none" : "block", - }; - } - // if (trusted) return; - self.timer(1s, watch_trust); -} - -class PasswordEyeArea : Reactor.Component { - render() { - return -
    - - {svg_eye} -
    ; - } - - event mouseenter { - var me = this; - me.leaved = false; - me.timer(300ms, function() { - if (me.leaved) return; - me.input.value = handler.get_password(); - }); - } - - event mouseleave { - this.leaved = true; - this.input.value = "******"; - } -} - -class Password: Reactor.Component { - function render() { - return
    - - {svg_edit} -
    ; - } - - event click $(svg#edit) (_, me) { - var menu = $(menu#edit-password-context); - me.popup(menu); - } - - event click $(li#refresh-password) { - handler.update_password(""); - this.update(); - } - - event click $(li#set-password) { - var me = this; - msgbox("custom-password", translate("Set Password"), "
    \ -
    " + translate('Password') + ":
    \ -
    " + translate('Confirmation') + ":
    \ -
    \ - ", function(res=null) { - if (!res) return; - var p0 = (res.password || "").trim(); - var p1 = (res.confirmation || "").trim(); - if (p0.length < 6) { - return translate("Too short, at least 6 characters."); - } - if (p0 != p1) { - return translate("The confirmation is not identical."); - } - handler.update_password(p0); - me.update(); - }); - } -} - -class ID: Reactor.Component { - function render() { - return ; - } - - // https://github.com/c-smile/sciter-sdk/blob/master/doc/content/sciter/Event.htm - event change { - var fid = formatId(this.value); - var d = this.value.length - (this.old_value || "").length; - this.old_value = this.value; - var start = this.xcall(#selectionStart) || 0; - var end = this.xcall(#selectionEnd); - if (fid == this.value || d <= 0 || start != end) { - return; - } - // fix Caret position - this.value = fid; - var text_after_caret = this.old_value.substr(start); - var n = fid.length - formatId(text_after_caret).length; - this.xcall(#setSelection, n, n); - } -} - -var reg = /^\d+$/; -function formatId(id) { - id = id.replace(/\s/g, ""); - if (reg.test(id) && id.length > 3) { - var n = id.length; - var a = n % 3 || 3; - var new_id = id.substr(0, a); - for (var i = a; i < n; i += 3) { - new_id += " " + id.substr(i, 3); - } - return new_id; - } - return id; -} - -event keydown (evt) { - if (!evt.shortcutKey) { - if (evt.keyCode == Event.VK_ENTER || - (is_osx && evt.keyCode == 0x4C) || - (is_linux && evt.keyCode == 65421)) { - var el = $(button#connect); - view.focus = el; - el.sendEvent("click"); - // simulate button click effect, windows does not have this issue - el.attributes.toggleClass("active", true); - self.timer(0.3s, function() { - el.attributes.toggleClass("active", false); - }); - } - } -} - -$(body).content(); - -function self.closing() { - // return false; // can prevent window close - var (x, y, w, h) = view.box(#rectw, #border, #screen); - handler.save_size(x, y, w, h); -} - -function self.ready() { - var r = handler.get_size(); - if (isReasonableSize(r) && r[2] > 0) { - view.move(r[0], r[1], r[2], r[3]); - } else { - centerize(800, 600); - } - if (!handler.get_remote_id()) { - view.focus = $(#remote_id); - } -} - -function checkConnectStatus() { - self.timer(1s, function() { - var tmp = !!handler.get_option("stop-service"); - if (tmp != service_stopped) { - service_stopped = tmp; - app.connect_status.update(); - myIdMenu.update(); - } - tmp = handler.get_connect_status(); - if (tmp[0] != connect_status) { - connect_status = tmp[0]; - app.connect_status.update(); - } - if (tmp[1] != key_confirmed) { - key_confirmed = tmp[1]; - app.update(); - } - if (tmp[2] && tmp[2] != my_id) { - stdout.println("id updated"); - app.update(); - } - tmp = handler.get_error(); - if (system_error != tmp) { - system_error = tmp; - app.update(); - } - tmp = handler.get_software_update_url(); - if (tmp != software_update_url) { - software_update_url = tmp; - app.update(); - } - if (handler.recent_sessions_updated()) { - stdout.println("recent sessions updated"); - app.update(); - } - checkConnectStatus(); - }); -} - -checkConnectStatus(); diff --git a/src/ui/install.tis b/src/ui/install.js similarity index 100% rename from src/ui/install.tis rename to src/ui/install.js diff --git a/src/ui/msgbox.html b/src/ui/msgbox.html index 79fa067b3..e8b9d02a7 100644 --- a/src/ui/msgbox.html +++ b/src/ui/msgbox.html @@ -1,7 +1,7 @@ + - + diff --git a/src/ui/msgbox.tis b/src/ui/msgbox.js similarity index 54% rename from src/ui/msgbox.tis rename to src/ui/msgbox.js index eb0ef8a62..04f53254d 100644 --- a/src/ui/msgbox.tis +++ b/src/ui/msgbox.js @@ -1,6 +1,8 @@ +import { PasswordComponent } from "./common"; +import {$,$$} from "@sciter"; //TEST $$ import +const view = Window.this; var type, title, text, getParams, remember, retry, callback, contentStyle; -var my_translate; - +var my_translate; // TEST function updateParams(params) { type = params.type; title = params.title; @@ -11,18 +13,17 @@ function updateParams(params) { my_translate = params.translate; retry = params.retry; contentStyle = params.contentStyle; + try { text = translate_text(text); } catch (e) {} if (retry > 0) { - self.timer(retry * 1000, function() { - view.close({ reconnect: true }); - }); + setTimeout(()=>view.close({ reconnect: true }),retry * 1000);// TEST } } function translate_text(text) { if (text.indexOf('Failed') == 0 && text.indexOf(': ') > 0) { - var fds = text.split(': '); - for (var i = 0; i < fds.length; ++i) { + let fds = text.split(': '); + for (let i = 0; i < fds.length; ++i) { fds[i] = my_translate(fds[i]); } text = fds.join(': '); @@ -30,17 +31,17 @@ function translate_text(text) { return text; } -var params = view.parameters; +var params = view.parameters; // TEST updateParams(params); var body; -class Body: Reactor.Component { - function this() { +class Body extends Element { + this() { body = this; } - function getIcon(color) { + getIcon(color) { if (type == "input-password") { return ; } @@ -56,23 +57,24 @@ class Body: Reactor.Component { return ; } - function getInputPasswordContent() { + getInputPasswordContent() { var ts = remember ? { checked: true } : {}; - return
    + //
    {my_translate('Remember password')}
    + return
    {my_translate('Please enter your password')}
    -
    {my_translate('Remember password')}
    +
    ; } - function getContent() { + getContent() { if (type == "input-password") { return this.getInputPasswordContent(); } return text; } - function getColor() { + getColor() { if (type == "input-password") { return "#AD448E"; } @@ -85,26 +87,20 @@ class Body: Reactor.Component { return "#2C8CFF"; } - function hasSkip() { + hasSkip() { return type.indexOf("skip") >= 0; } - function render() { - var color = this.getColor(); - var icon = this.getIcon(color); - var content = this.getContent(); - var hasCancel = type.indexOf("error") < 0 && type != "success" && type.indexOf("nocancel") < 0; - var hasOk = type != "connecting" && type.indexOf("nook") < 0; - var hasClose = type.indexOf("hasclose") >= 0; - var show_progress = type == "connecting"; - self.style.set { border: color + " solid 1px" }; - var me = this; - self.timer(1ms, function() { - if (typeof content == "string") - me.$(#content).html = my_translate(content); - else - me.$(#content).content(content); - }); + render() { + let color = this.getColor(); + let icon = this.getIcon(color); + let content = this.getContent(); + let hasCancel = type.indexOf("error") < 0 && type != "success" && type.indexOf("nocancel") < 0; + let hasOk = type != "connecting" && type.indexOf("nook") < 0; + let hasClose = type.indexOf("hasclose") >= 0; + let show_progress = type == "connecting"; + document.body.style.setProperty("border",(color + " solid 1px")); + setTimeout(()=>this.$("#content").content(my_translate(content)),1); return (
    @@ -113,72 +109,110 @@ class Body: Reactor.Component {
    {icon &&
    {icon}
    } -
    +
    - - - {hasCancel || hasRetry ? : ""} - {this.hasSkip() ? : ""} - {hasOk || hasRetry ? : ""} - {hasClose ? : ""} + + + {hasCancel || retry ? : ""} + {this.hasSkip() ? : ""} + {hasOk || retry ? : ""} + {hasClose ? : ""}
    ); } - event click $(.custom-event) (_, me) { + // TEST + ["on click at .custom-event"](_, me) { if (callback) callback(me); } -} -$(body).content(); + ["on click at button#cancel"]() { + view.close(); + if (callback) callback(null); + } + + ["on click at button#skip"]() { + let values = getValues(); + values.skip = true; + view.close(values); + if (callback) callback(values); + } + + ["on click at button#submit"](){ + if (type == "error") { + if (retry) { + view.close({ reconnect: true }); + } else { + view.close(); + if (callback) callback(null); + } + return; + } + if (type == "re-input-password") { + type = "input-password"; + body.componentUpdate(); + set_outline_focus(); + return; + } + var values = getValues(); + if (callback) { + var err = callback(values, show_progress); + if (err && !err.trim()) { + return; + } + if (err) { + show_progress(false, err); + return; + } + } + view.close(values); + } + ["on keydown"] (evt) { // TEST + if (!evt.shortcutKey) { + // TODO TEST Windows/Mac + if (evt.code == "KeyRETURN") { + submit(); + } + if (evt.code == "KeyESCAPE") { + cancel(); + } + } + } +} function show_progress(show=1, err="") { if (show == -1) { view.close() return; } - $(#progress).style.set { - display: show ? "inline-block" : "none" - }; - $(#error).text = err; + $("#progress").style.setProperty("display",show ? "inline-block" : "none"); + $("#error").text = err; } function submit() { - if ($(button#submit)) { - $(button#submit).sendEvent("click"); + if ($("button#submit")) { + $("button#submit").click(); // TEST } } function cancel() { - if ($(button#cancel)) { - $(button#cancel).sendEvent("click"); + if ($("button#cancel")) { + $("button#cancel").click(); } } -event click $(button#cancel) { - view.close(); - if (callback) callback(null); -} - -event click $(button#skip) { - var values = getValues(); - values.skip = true; - view.close(values); - if (callback) callback(values); -} - function getValues() { - var values = { type: type }; - for (var el in $$(.form input)) { - values[el.attributes["name"]] = el.value; + let values = { type: type }; + for (let el of $$(".form input")) { + values[el.getAttribute("name")] = el.value; } - for (var el in $$(.form textarea)) { - values[el.attributes["name"]] = el.value; + for (let el of $$(".form textarea")) { + values[el.getAttribute("name")] = el.value; } - for (var el in $$(.form button)) { - values[el.attributes["name"]] = el.value; + for (let el of $$(".form button")) { + values[el.getAttribute("name")] = el.value; } if (type == "input-password") { values.password = (values.password || "").trim(); @@ -189,76 +223,31 @@ function getValues() { return values; } -event click $(button#submit) { - if (type == "error") { - if (hasRetry) { - view.close({ reconnect: true }); - } else { - view.close(); - if (callback) callback(null); - } - return; - } - if (type == "re-input-password") { - type = "input-password"; - body.update(); - set_outline_focus(); - return; - } - var values = getValues(); - if (callback) { - var err = callback(values, show_progress); - if (err && !err.trim()) { - return; - } - if (err) { - show_progress(false, err); - return; - } - } - view.close(values); -} - -event keydown (evt) { - if (!evt.shortcutKey) { - if (evt.keyCode == Event.VK_ENTER || - (is_osx && evt.keyCode == 0x4C) || - (is_linux && evt.keyCode == 65421)) { - submit(); - } - if (evt.keyCode == Event.VK_ESCAPE) { - cancel(); - } - } -} - function set_outline_focus() { - self.timer(30ms, function() { - var el = $(.outline-focus); + setTimeout(function() { + let el = $(".outline-focus"); if (el) view.focus = el; else { - el = $(#submit); + el = $("#submit"); if (el) view.focus = el; } - }); + },30); } set_outline_focus(); -function checkParams() { - self.timer(30ms, function() { - var tmp = getParams(); - if (!tmp || !tmp.type) { - view.close("!alive"); - return; - } else if (tmp != params) { - params = tmp; - updateParams(params); - body.update(); - set_outline_focus(); - } - checkParams(); - }); -} +// checkParams +setInterval(function() { + let tmp = getParams(); + if (!tmp || !tmp.type) { + view.close("!alive"); + return; + } else if (tmp != params) { + params = tmp; + updateParams(params); + body.componentUpdate(); + set_outline_focus(); + } +},30); -checkParams(); +document.body.content(); \ No newline at end of file diff --git a/src/ui/port_forward.js b/src/ui/port_forward.js new file mode 100644 index 000000000..c1ab807a2 --- /dev/null +++ b/src/ui/port_forward.js @@ -0,0 +1,81 @@ +import { translate, handler } from "./common.js"; +import { svg_arrow, svg_cancel } from "./file_transfer.js"; + +class PortForward extends Element { + render() { + let args = handler.xcall("get_args"); + let is_rdp = handler.xcall("is_rdp"); + if (is_rdp) { + this.pfs = [["", "", "RDP"]]; + args = ["rdp"]; + } else if (args.length) { + this.pfs = [args]; + } else { + this.pfs = handler.xcall("get_port_forwards"); + } + let pfs = this.pfs.map(function(pf, i) { + return ( + {is_rdp ? : pf[0]} + {args.length ? svg_arrow : ""} + {pf[1] || "localhost"} + {pf[2]} + {args.length ? "" : {svg_cancel}} + ); + }); + return
    + {pfs.length ?
    + {translate('Listening ...')}
    + {translate('not_close_tcp_tip')} +
    : ""} + + + + + + + {args.length ? "" : } + + + + {args.length ? "" : + + + + + + + + } + {pfs} + +
    {translate('Local Port')} + {translate('Remote Host')}{translate('Remote Port')}{translate('Action')}
    {svg_arrow}
    ; + } + + ["on click at #add"] () { + let port = ($("#port").value || "").toInteger() || 0; // TODO toInteger + let remote_host = $("#remote-host").value || ""; + let remote_port = ($("#remote-port").value || "").toInteger() || 0; // TODO toInteger + if (port <= 0 || remote_port <= 0) return; + handler.xcall("add_port_forward",port, remote_host, remote_port); + this.componentUpdate(); + } + + ["on click at #new-rdp"] () { + handler.xcall("new_rdp"); + } + + ["on click at .remove svg"](_, me) { + let pf = this.pfs[me.parentElement.parentElement.index - 1]; + handler.xcall("remove_port_forward",pf[0]); + this.componentUpdate(); + } +} + +export function initializePortForward() +{ + document.$("#file-transfer-wrapper").content(); + document.$("#video-wrapper").style.setProperty("visibility","hidden"); + document.$("#video-wrapper").style.setProperty("position","absolute") + document.$("#file-transfer-wrapper").style.setProperty("display","block"); +} diff --git a/src/ui/port_forward.tis b/src/ui/port_forward.tis deleted file mode 100644 index a30f698c5..000000000 --- a/src/ui/port_forward.tis +++ /dev/null @@ -1,77 +0,0 @@ -class PortForward: Reactor.Component { - function render() { - var args = handler.get_args(); - var is_rdp = handler.is_rdp(); - if (is_rdp) { - this.pfs = [["", "", "RDP"]]; - args = ["rdp"]; - } else if (args.length) { - this.pfs = [args]; - } else { - this.pfs = handler.get_port_forwards(); - } - var pfs = this.pfs.map(function(pf, i) { - return - {is_rdp ? : pf[0]} - {args.length ? svg_arrow : ""} - {pf[1] || "localhost"} - {pf[2]} - {args.length ? "" : {svg_cancel}} - ; - }); - return
    - {pfs.length ?
    - {translate('Listening ...')}
    - {translate('not_close_tcp_tip')} -
    : ""} - - - - - - - {args.length ? "" : } - - - - {args.length ? "" : - - - - - - - - } - {pfs} - -
    {translate('Local Port')} - {translate('Remote Host')}{translate('Remote Port')}{translate('Action')}
    {svg_arrow}
    ; - } - - event click $(#add) () { - var port = ($(#port).value || "").toInteger() || 0; - var remote_host = $(#remote-host).value || ""; - var remote_port = ($(#remote-port).value || "").toInteger() || 0; - if (port <= 0 || remote_port <= 0) return; - handler.add_port_forward(port, remote_host, remote_port); - this.update(); - } - - event click $(#new-rdp) { - handler.new_rdp(); - } - - event click $(.remove svg) (_, me) { - var pf = this.pfs[me.parent.parent.index - 1]; - handler.remove_port_forward(pf[0]); - this.update(); - } -} - -function initializePortForward() -{ - $(#file-transfer-wrapper).content(); - $(#video-wrapper).style.set { visibility: "hidden", position: "absolute" }; - $(#file-transfer-wrapper).style.set { display: "block" }; -} diff --git a/src/ui/remote.html b/src/ui/remote.html index 810b63515..3a740172e 100644 --- a/src/ui/remote.html +++ b/src/ui/remote.html @@ -1,19 +1,24 @@ - - +
    diff --git a/src/ui/remote.js b/src/ui/remote.js new file mode 100644 index 000000000..d434dffe5 --- /dev/null +++ b/src/ui/remote.js @@ -0,0 +1,470 @@ +import { $ } from "@sciter"; +import { handler,view,isReasonableSize,msgbox,is_osx, is_linux, centerize, connecting } from "./common.js"; +import { initializeFileTransfer, save_file_transfer_close_state } from "./file_transfer.js"; +import { initializePortForward } from "./port_forward.js"; +import { header } from "./header.js"; + +const body = document.body; +var cursor_img = $("img#cursor"); +var last_key_time = 0; +var display_width = 0; +var display_height = 0; +var display_origin_x = 0; +var display_origin_y = 0; +var display_scale = 1; +export var keyboard_enabled = true; // server side +export var clipboard_enabled = true; // server side +export var audio_enabled = true; // server side + +handler.is_port_forward = handler.xcall("is_port_forward"); +handler.is_file_transfer = handler.xcall("is_file_transfer"); + +handler.setDisplay = function(x, y, w, h) { + display_width = w; + display_height = h; + display_origin_x = x; + display_origin_y = y; + adaptDisplay(); +} + +export function adaptDisplay() { + let w = display_width; + let h = display_height; + if (!w || !h) return; + let style = handler.xcall("get_view_style"); + display_scale = 1.; + let [sx, sy, sw, sh] = view.screenBox(view.state == Window.WINDOW_FULL_SCREEN ? "frame" : "workarea", "rectw"); + if (sw >= w && sh > h) { + let hh = $("header").state.box("height", "border"); + let el = $("div#adjust-window"); + if (sh > h + hh && el) { + el.style.setProperty("display","block"); + el = $("li#adjust-window"); + el.style.setProperty("display","block"); + el.on("click",function() { + view.state = Window.WINDOW_SHOWN; + let [x, y] = body.state.box("position", "border", "screen"); // TEST + // extra for border + let extra = 2; + view.move(x, y, w + extra, h + hh + extra); + }) + } + } + if (style != "original") { + let bw = body.state.box("width", "border"); + let bh = body.state.box("height", "border"); + if (view.state == Window.WINDOW_FULL_SCREEN) { + bw = sw; + bh = sh; + } + if (bw > 0 && bh > 0) { + // TEST del toFloat() + let scale_x = bw / w; + let scale_y = bh / h; + let scale = scale_x < scale_y ? scale_x : scale_y; + if ((scale > 1 && style == "stretch") || + (scale < 1 && style == "shrink")) { + display_scale = scale; + w = w * scale; + h = h * scale; + } + } + } + handler.style.setProperty("width",w + "px"); + handler.style.setProperty("height",h + "px") +} + +// https://sciter.com/event-handling/ +// https://sciter.com/docs/content/sciter/Event.htm + +var entered = false; + +// TODO ! +// var keymap = {}; +// for (var (k, v) in Event) { +// k = k + "" +// if (k[0] == "V" && k[1] == "K") { +// keymap[v] = k; +// } +// } + +// VK_ENTER = VK_RETURN +// somehow, handler.onKey and view.onKey not working +// function self.onKey(evt) { +// last_key_time = getTime(); +// if (is_file_transfer || is_port_forward) return false; +// if (!entered) return false; +// if (!keyboard_enabled) return false; +// switch (evt.type) { +// case Event.KEY_DOWN: +// handler.key_down_or_up(1, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); +// if (is_osx && evt.commandKey) { +// handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); +// } +// break; +// case Event.KEY_UP: +// handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); +// break; +// case Event.KEY_CHAR: +// // the keypress event is fired when the element receives character value. Event.keyCode is a UNICODE code point of the character +// handler.key_down_or_up(2, "", evt.keyCode, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); +// break; +// default: +// return false; +// } +// return true; +// } + +var wait_window_toolbar = false; +var last_mouse_mask; +var acc_wheel_delta_x = 0; +var acc_wheel_delta_y = 0; +var last_wheel_time = 0; +var inertia_velocity_x = 0; +var inertia_velocity_y = 0; +var acc_wheel_delta_x0 = 0; +var acc_wheel_delta_y0 = 0; +var total_wheel_time = 0; +var wheeling = false; +var dragging = false; + +// https://stackoverflow.com/questions/5833399/calculating-scroll-inertia-momentum +function resetWheel() { + acc_wheel_delta_x = 0; + acc_wheel_delta_y = 0; + last_wheel_time = 0; + inertia_velocity_x = 0; + inertia_velocity_y = 0; + acc_wheel_delta_x0 = 0; + acc_wheel_delta_y0 = 0; + total_wheel_time = 0; + wheeling = false; +} + +var INERTIA_ACCELERATION = 30; + +// not good, precision not enough to simulate accelation effect, +// seems have to use pixel based rather line based delta +function accWheel(v, is_x) { + if (wheeling) return; + var abs_v = Math.abs(v); + var max_t = abs_v / INERTIA_ACCELERATION; + for (var t = 0.1; t < max_t; t += 0.1) { + var d = Math.round((abs_v - t * INERTIA_ACCELERATION / 2) * t).toInteger(); + if (d >= 1) { + abs_v -= t * INERTIA_ACCELERATION; + if (v < 0) { + d = -d; + v = -abs_v; + } else { + v = abs_v; + } + handler.xcall("send_mouse",3, is_x ? d : 0, !is_x ? d : 0, false, false, false, false); + accWheel(v, is_x); + break; + } + } +} + +// // TODO +// handler.onMouse(evt) +// { +// if (is_file_transfer || is_port_forward) return false; +// if (view.windowState == View.WINDOW_FULL_SCREEN && !dragging) { +// var dy = evt.y - scroll_body.scroll(#top); +// if (dy <= 1) { +// if (!wait_window_toolbar) { +// wait_window_toolbar = true; +// self.timer(300ms, function() { +// if (!wait_window_toolbar) return; +// var extra = 0; +// // workaround for stupid Sciter, without this, click +// // event not triggered on top part of buttons on toolbar +// if (is_osx) extra = 10; +// if (view.windowState == View.WINDOW_FULL_SCREEN) { +// $(header).style.set { +// display: "block", +// padding: (2 * workarea_offset + extra) + "px 0 0 0", +// }; +// } +// wait_window_toolbar = false; +// }); +// } +// } else { +// wait_window_toolbar = false; +// var h = $(header).style; +// if (dy > 20 && h#display != "none") { +// h.set { +// display: "none", +// }; +// } +// } +// } +// if (!got_mouse_control) { +// if (Math.abs(evt.x - cur_local_x) > 12 || Math.abs(evt.y - cur_local_y) > 12) { +// got_mouse_control = true; +// } else { +// return; +// } +// } +// var mask = 0; +// var wheel_delta_x; +// var wheel_delta_y; +// switch(evt.type) { +// case Event.MOUSE_DOWN: +// mask = 1; +// dragging = true; +// break; +// case Event.MOUSE_UP: +// mask = 2; +// dragging = false; +// break; +// case Event.MOUSE_MOVE: +// if (cursor_img.style#display != "none" && keyboard_enabled) { +// cursor_img.style#display = "none"; +// handler.style#cursor = ''; +// } +// break; +// case Event.MOUSE_WHEEL: +// // mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"]; +// mask = 3; +// { +// var (dx, dy) = evt.wheelDeltas; +// if (dx > 0) dx = 1; +// else if (dx < 0) dx = -1; +// if (dy > 0) dy = 1; +// else if (dy < 0) dy = -1; +// if (Math.abs(dx) > Math.abs(dy)) { +// dy = 0; +// } else { +// dx = 0; +// } +// acc_wheel_delta_x += dx; +// acc_wheel_delta_y += dy; +// wheel_delta_x = acc_wheel_delta_x.toInteger(); +// wheel_delta_y = acc_wheel_delta_y.toInteger(); +// acc_wheel_delta_x -= wheel_delta_x; +// acc_wheel_delta_y -= wheel_delta_y; +// var now = getTime(); +// var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0; +// if (dt > 0) { +// var vx = dx / dt; +// var vy = dy / dt; +// if (vx != 0 || vy != 0) { +// inertia_velocity_x = vx; +// inertia_velocity_y = vy; +// } +// } +// acc_wheel_delta_x0 += dx; +// acc_wheel_delta_y0 += dy; +// total_wheel_time += dt; +// if (dx == 0 && dy == 0) { +// wheeling = false; +// if (dt < 0.1 && total_wheel_time > 0) { +// var v2 = (acc_wheel_delta_y0 / total_wheel_time) * inertia_velocity_y; +// if (v2 > 0) { +// v2 = Math.sqrt(v2); +// inertia_velocity_y = inertia_velocity_y < 0 ? -v2 : v2; +// accWheel(inertia_velocity_y, false); +// } +// v2 = (acc_wheel_delta_x0 / total_wheel_time) * inertia_velocity_x; +// if (v2 > 0) { +// v2 = Math.sqrt(v2); +// inertia_velocity_x = inertia_velocity_x < 0 ? -v2 : v2; +// accWheel(inertia_velocity_x, true); +// } +// } +// resetWheel(); +// } else { +// wheeling = true; +// } +// last_wheel_time = now; +// if (wheel_delta_x == 0 && wheel_delta_y == 0) return keyboard_enabled; +// } +// break; +// case Event.MOUSE_DCLICK: // seq: down, up, dclick, up +// mask = 1; +// break; +// case Event.MOUSE_ENTER: +// entered = true; +// console.log("enter"); +// return keyboard_enabled; +// case Event.MOUSE_LEAVE: +// entered = false; +// console.log("leave"); +// return keyboard_enabled; +// default: +// return false; +// } +// var x = evt.x; +// var y = evt.y; +// if (mask != 0) { +// // to gain control of the mouse, user must move mouse +// if (cur_x != x || cur_y != y) { +// return keyboard_enabled; +// } +// // save bandwidth +// x = 0; +// y = 0; +// } else { +// cur_local_x = cur_x = x; +// cur_local_y = cur_y = y; +// } +// if (mask != 3) { +// resetWheel(); +// } +// if (!keyboard_enabled) return false; +// x = (x / display_scale).toInteger(); +// y = (y / display_scale).toInteger(); +// // insert down between two up, osx has this behavior for triple click +// if (last_mouse_mask == 2 && mask == 2) { +// handler.send_mouse((evt.buttons << 3) | 1, x + display_origin_x, y + display_origin_y, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey); +// } +// last_mouse_mask = mask; +// // to-do: altKey, ctrlKey etc +// handler.send_mouse((evt.buttons << 3) | mask, +// mask == 3 ? wheel_delta_x : x + display_origin_x, +// mask == 3 ? wheel_delta_y : y + display_origin_y, +// evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey); +// return true; +// }; + +var cur_hotx = 0; +var cur_hoty = 0; +var cur_img = null; +var cur_x = 0; +var cur_y = 0; +var cur_local_x = 0; +var cur_local_y = 0; +var cursors = {}; +var image_binded; + +handler.setCursorData = function(id, hotx, hoty, width, height, colors) { + cur_hotx = hotx; + cur_hoty = hoty; + cursor_img.style.setProperty("width",width + "px"); + cursor_img.style.setProperty("height",height + "px"); + + let img = Image.fromBytes(colors); + if (img) { + image_binded = true; + cursors[id] = [img, hotx, hoty, width, height]; + this.bindImage("in-memory:cursor", img); + if (cursor_img.style.getProperty("display") == 'none') { // TEST getProperty + setTimeout(function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty) },1); // TODO cursor + } + cur_img = img; + } +} + +handler.setCursorId = function(id) { + let img = cursors[id]; + if (img) { + image_binded = true; + cur_hotx = img[1]; + cur_hoty = img[2]; + cursor_img.style.setProperty("width",img[3] + "px"); + cursor_img.style.setProperty("height",img[4] + "px"); + img = img[0]; + this.bindImage("in-memory:cursor", img); + if (cursor_img.style.getProperty("display") == 'none') { + setTimeout(function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty) },1); + } + cur_img = img; + } +} + +var got_mouse_control = true; +handler.setCursorPosition = function(x, y) { + if (!image_binded) return; + got_mouse_control = false; + cur_x = x - display_origin_x; + cur_y = y - display_origin_y; + var x = cur_x - cur_hotx; + var y = cur_y - cur_hoty; + x *= display_scale; + y *= display_scale; + cursor_img.style.setProperty("width",x + "px"); + cursor_img.style.setProperty("width",y + "px"); + if (cursor_img.style.getProperty("display") == 'none') { + handler.style.setProperty("cursor",'none'); + cursor_img.style.setProperty("display","block"); + } +} + +document.on("ready",()=> { + let w = 960; + let h = 640; + if (handler.is_file_transfer || handler.is_port_forward) { + let r = handler.xcall("get_size"); + if (isReasonableSize(r) && r[2] > 0) { + view.move(r[0], r[1], r[2], r[3]); + } else { + centerize(w, h); + } + } else { + centerize(w, h); + } + if (!handler.is_port_forward) connecting(); + if (handler.is_file_transfer) initializeFileTransfer(); + if (handler.is_port_forward) initializePortForward(); +}) + +var workarea_offset = 0; +var size_adapted; +handler.adaptSize = function() { + if (size_adapted) return; + size_adapted = true; + let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw"); + let [fx, fy, fw, fh] = view.screenBox("frame", "rectw"); + if (is_osx) workarea_offset = sy; + let r = handler.xcall("get_size"); + if (isReasonableSize(r) && r[2] > 0) { + if (r[2] >= fw && r[3] >= fh && !is_linux) { + view.state = Window.WINDOW_FULL_SCREEN; + console.log("Initialize to full screen"); + } else if (r[2] >= sw && r[3] >= sh) { + view.state = Window.WINDOW_MAXIMIZED; + console.log("Initialize to full screen"); + } else { + view.move(r[0], r[1], r[2], r[3]); + } + } else { + let w = handler.state.box("width", "border") + let h = handler.state.box("height", "border") + if (w >= sw || h >= sh) { + view.state = Window.WINDOW_MAXIMIZED; + return; + } + // extra for border + let extra = 2; + centerize(w + extra, handler.state.box("height", "border") + h + extra); + } +} + +document.on("unloadequest",()=> { + let [x, y, w, h] = body.state.box("rectw", "border", "screen"); + console.log("remote.js unloadequest",x, y, w, h) + if (handler.is_file_transfer) save_file_transfer_close_state(); + if (handler.is_file_transfer || handler.is_port_forward || size_adapted) handler.xcall("save_size",x, y, w, h); +}) + +handler.setPermission = function(name, enabled) { + if (name == "keyboard") keyboard_enabled = enabled; + if (name == "audio") audio_enabled = enabled; + if (name == "clipboard") clipboard_enabled = enabled; + header.componentUpdate(); +} + +// TODO +handler.closeSuccess = function() { + // handler.msgbox("success", "Successful", "Ready to go."); + console.log("remote.js handler.closeSuccess"); + handler.msgbox("", "", ""); +} diff --git a/src/ui/remote.rs b/src/ui/remote.rs index d4fde3f38..22be7d038 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -75,7 +75,7 @@ impl Deref for Handler { impl sciter::EventHandler for Handler { fn get_subscription(&mut self) -> Option { - Some(EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT) + Some(EVENT_GROUPS::HANDLE_METHOD_CALL | EVENT_GROUPS::HANDLE_SCRIPTING_METHOD_CALL | EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT) } fn attached(&mut self, root: HELEMENT) { diff --git a/src/ui/remote.tis b/src/ui/remote.tis deleted file mode 100644 index af936e112..000000000 --- a/src/ui/remote.tis +++ /dev/null @@ -1,464 +0,0 @@ -var cursor_img = $(img#cursor); -var last_key_time = 0; -is_file_transfer = handler.is_file_transfer(); -var is_port_forward = handler.is_port_forward(); -var display_width = 0; -var display_height = 0; -var display_origin_x = 0; -var display_origin_y = 0; -var display_scale = 1; -var keyboard_enabled = true; // server side -var clipboard_enabled = true; // server side -var audio_enabled = true; // server side -var scroll_body = $(body); - -handler.setDisplay = function(x, y, w, h) { - display_width = w; - display_height = h; - display_origin_x = x; - display_origin_y = y; - adaptDisplay(); -} - -function adaptDisplay() { - var w = display_width; - var h = display_height; - if (!w || !h) return; - var style = handler.get_view_style(); - display_scale = 1.; - var (sx, sy, sw, sh) = view.screenBox(view.windowState == View.WINDOW_FULL_SCREEN ? #frame : #workarea, #rectw); - if (sw >= w && sh > h) { - var hh = $(header).box(#height, #border); - var el = $(div#adjust-window); - if (sh > h + hh && el) { - el.style.set{ display: "block" }; - el = $(li#adjust-window); - el.style.set{ display: "block" }; - el.onClick = function() { - view.windowState == View.WINDOW_SHOWN; - var (x, y) = view.box(#position, #border, #screen); - // extra for border - var extra = 2; - view.move(x, y, w + extra, h + hh + extra); - } - } - } - if (style != "original") { - var bw = $(body).box(#width, #border); - var bh = $(body).box(#height, #border); - if (view.windowState == View.WINDOW_FULL_SCREEN) { - bw = sw; - bh = sh; - } - if (bw > 0 && bh > 0) { - var scale_x = bw.toFloat() / w; - var scale_y = bh.toFloat() / h; - var scale = scale_x < scale_y ? scale_x : scale_y; - if ((scale > 1 && style == "stretch") || - (scale < 1 && style == "shrink")) { - display_scale = scale; - w = w * scale; - h = h * scale; - } - } - } - handler.style.set { - width: w + "px", - height: h + "px", - }; -} - -// https://sciter.com/event-handling/ -// https://sciter.com/docs/content/sciter/Event.htm - -var entered = false; - -var keymap = {}; -for (var (k, v) in Event) { - k = k + "" - if (k[0] == "V" && k[1] == "K") { - keymap[v] = k; - } -} - -// VK_ENTER = VK_RETURN -// somehow, handler.onKey and view.onKey not working -function self.onKey(evt) { - last_key_time = getTime(); - if (is_file_transfer || is_port_forward) return false; - if (!entered) return false; - if (!keyboard_enabled) return false; - switch (evt.type) { - case Event.KEY_DOWN: - handler.key_down_or_up(1, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); - if (is_osx && evt.commandKey) { - handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); - } - break; - case Event.KEY_UP: - handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); - break; - case Event.KEY_CHAR: - // the keypress event is fired when the element receives character value. Event.keyCode is a UNICODE code point of the character - handler.key_down_or_up(2, "", evt.keyCode, evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); - break; - default: - return false; - } - return true; -} - -var wait_window_toolbar = false; -var last_mouse_mask; -var acc_wheel_delta_x = 0; -var acc_wheel_delta_y = 0; -var last_wheel_time = 0; -var inertia_velocity_x = 0; -var inertia_velocity_y = 0; -var acc_wheel_delta_x0 = 0; -var acc_wheel_delta_y0 = 0; -var total_wheel_time = 0; -var wheeling = false; -var dragging = false; - -// https://stackoverflow.com/questions/5833399/calculating-scroll-inertia-momentum -function resetWheel() { - acc_wheel_delta_x = 0; - acc_wheel_delta_y = 0; - last_wheel_time = 0; - inertia_velocity_x = 0; - inertia_velocity_y = 0; - acc_wheel_delta_x0 = 0; - acc_wheel_delta_y0 = 0; - total_wheel_time = 0; - wheeling = false; -} - -var INERTIA_ACCELERATION = 30; - -// not good, precision not enough to simulate accelation effect, -// seems have to use pixel based rather line based delta -function accWheel(v, is_x) { - if (wheeling) return; - var abs_v = Math.abs(v); - var max_t = abs_v / INERTIA_ACCELERATION; - for (var t = 0.1; t < max_t; t += 0.1) { - var d = Math.round((abs_v - t * INERTIA_ACCELERATION / 2) * t).toInteger(); - if (d >= 1) { - abs_v -= t * INERTIA_ACCELERATION; - if (v < 0) { - d = -d; - v = -abs_v; - } else { - v = abs_v; - } - handler.send_mouse(3, is_x ? d : 0, !is_x ? d : 0, false, false, false, false); - accWheel(v, is_x); - break; - } - } -} - -function handler.onMouse(evt) -{ - if (is_file_transfer || is_port_forward) return false; - if (view.windowState == View.WINDOW_FULL_SCREEN && !dragging) { - var dy = evt.y - scroll_body.scroll(#top); - if (dy <= 1) { - if (!wait_window_toolbar) { - wait_window_toolbar = true; - self.timer(300ms, function() { - if (!wait_window_toolbar) return; - var extra = 0; - // workaround for stupid Sciter, without this, click - // event not triggered on top part of buttons on toolbar - if (is_osx) extra = 10; - if (view.windowState == View.WINDOW_FULL_SCREEN) { - $(header).style.set { - display: "block", - padding: (2 * workarea_offset + extra) + "px 0 0 0", - }; - } - wait_window_toolbar = false; - }); - } - } else { - wait_window_toolbar = false; - var h = $(header).style; - if (dy > 20 && h#display != "none") { - h.set { - display: "none", - }; - } - } - } - if (!got_mouse_control) { - if (Math.abs(evt.x - cur_local_x) > 12 || Math.abs(evt.y - cur_local_y) > 12) { - got_mouse_control = true; - } else { - return; - } - } - var mask = 0; - var wheel_delta_x; - var wheel_delta_y; - switch(evt.type) { - case Event.MOUSE_DOWN: - mask = 1; - dragging = true; - break; - case Event.MOUSE_UP: - mask = 2; - dragging = false; - break; - case Event.MOUSE_MOVE: - if (cursor_img.style#display != "none" && keyboard_enabled) { - cursor_img.style#display = "none"; - handler.style#cursor = ''; - } - break; - case Event.MOUSE_WHEEL: - // mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"]; - mask = 3; - { - var (dx, dy) = evt.wheelDeltas; - if (dx > 0) dx = 1; - else if (dx < 0) dx = -1; - if (dy > 0) dy = 1; - else if (dy < 0) dy = -1; - if (Math.abs(dx) > Math.abs(dy)) { - dy = 0; - } else { - dx = 0; - } - acc_wheel_delta_x += dx; - acc_wheel_delta_y += dy; - wheel_delta_x = acc_wheel_delta_x.toInteger(); - wheel_delta_y = acc_wheel_delta_y.toInteger(); - acc_wheel_delta_x -= wheel_delta_x; - acc_wheel_delta_y -= wheel_delta_y; - var now = getTime(); - var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0; - if (dt > 0) { - var vx = dx / dt; - var vy = dy / dt; - if (vx != 0 || vy != 0) { - inertia_velocity_x = vx; - inertia_velocity_y = vy; - } - } - acc_wheel_delta_x0 += dx; - acc_wheel_delta_y0 += dy; - total_wheel_time += dt; - if (dx == 0 && dy == 0) { - wheeling = false; - if (dt < 0.1 && total_wheel_time > 0) { - var v2 = (acc_wheel_delta_y0 / total_wheel_time) * inertia_velocity_y; - if (v2 > 0) { - v2 = Math.sqrt(v2); - inertia_velocity_y = inertia_velocity_y < 0 ? -v2 : v2; - accWheel(inertia_velocity_y, false); - } - v2 = (acc_wheel_delta_x0 / total_wheel_time) * inertia_velocity_x; - if (v2 > 0) { - v2 = Math.sqrt(v2); - inertia_velocity_x = inertia_velocity_x < 0 ? -v2 : v2; - accWheel(inertia_velocity_x, true); - } - } - resetWheel(); - } else { - wheeling = true; - } - last_wheel_time = now; - if (wheel_delta_x == 0 && wheel_delta_y == 0) return keyboard_enabled; - } - break; - case Event.MOUSE_DCLICK: // seq: down, up, dclick, up - mask = 1; - break; - case Event.MOUSE_ENTER: - entered = true; - stdout.println("enter"); - return keyboard_enabled; - case Event.MOUSE_LEAVE: - entered = false; - stdout.println("leave"); - return keyboard_enabled; - default: - return false; - } - var x = evt.x; - var y = evt.y; - if (mask != 0) { - // to gain control of the mouse, user must move mouse - if (cur_x != x || cur_y != y) { - return keyboard_enabled; - } - // save bandwidth - x = 0; - y = 0; - } else { - cur_local_x = cur_x = x; - cur_local_y = cur_y = y; - } - if (mask != 3) { - resetWheel(); - } - if (!keyboard_enabled) return false; - x = (x / display_scale).toInteger(); - y = (y / display_scale).toInteger(); - // insert down between two up, osx has this behavior for triple click - if (last_mouse_mask == 2 && mask == 2) { - handler.send_mouse((evt.buttons << 3) | 1, x + display_origin_x, y + display_origin_y, evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey); - } - last_mouse_mask = mask; - // to-do: altKey, ctrlKey etc - handler.send_mouse((evt.buttons << 3) | mask, - mask == 3 ? wheel_delta_x : x + display_origin_x, - mask == 3 ? wheel_delta_y : y + display_origin_y, - evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey); - return true; -}; - -var cur_hotx = 0; -var cur_hoty = 0; -var cur_img = null; -var cur_x = 0; -var cur_y = 0; -var cur_local_x = 0; -var cur_local_y = 0; -var cursors = {}; -var image_binded; - -handler.setCursorData = function(id, hotx, hoty, width, height, colors) { - cur_hotx = hotx; - cur_hoty = hoty; - cursor_img.style.set { - width: width + "px", - height: height + "px", - }; - var img = Image.fromBytes(colors); - if (img) { - image_binded = true; - cursors[id] = [img, hotx, hoty, width, height]; - this.bindImage("in-memory:cursor", img); - if (cursor_img.style#display == 'none') { - self.timer(1ms, function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty); }); - } - cur_img = img; - } -} - -handler.setCursorId = function(id) { - var img = cursors[id]; - if (img) { - image_binded = true; - cur_hotx = img[1]; - cur_hoty = img[2]; - cursor_img.style.set { - width: img[3] + "px", - height: img[4] + "px", - }; - img = img[0]; - this.bindImage("in-memory:cursor", img); - if (cursor_img.style#display == 'none') { - self.timer(1ms, function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty); }); - } - cur_img = img; - } -} - -var got_mouse_control = true; -handler.setCursorPosition = function(x, y) { - if (!image_binded) return; - got_mouse_control = false; - cur_x = x - display_origin_x; - cur_y = y - display_origin_y; - var x = cur_x - cur_hotx; - var y = cur_y - cur_hoty; - x *= display_scale; - y *= display_scale; - cursor_img.style.set { - left: x + "px", - top: y + "px", - }; - if (cursor_img.style#display == 'none') { - handler.style#cursor = 'none'; - cursor_img.style#display = "block"; - } -} - -function self.ready() { - var w = 960; - var h = 640; - if (is_file_transfer || is_port_forward) { - var r = handler.get_size(); - if (isReasonableSize(r) && r[2] > 0) { - view.move(r[0], r[1], r[2], r[3]); - } else { - centerize(w, h); - } - } else { - centerize(w, h); - } - if (!is_port_forward) connecting(); - if (is_file_transfer) initializeFileTransfer(); - if (is_port_forward) initializePortForward(); -} - -var workarea_offset = 0; -var size_adapted; -handler.adaptSize = function() { - if (size_adapted) return; - size_adapted = true; - var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw); - var (fx, fy, fw, fh) = view.screenBox(#frame, #rectw); - if (is_osx) workarea_offset = sy; - var r = handler.get_size(); - if (isReasonableSize(r) && r[2] > 0) { - if (r[2] >= fw && r[3] >= fh && !is_linux) { - view.windowState = View.WINDOW_FULL_SCREEN; - stdout.println("Initialize to full screen"); - } else if (r[2] >= sw && r[3] >= sh) { - view.windowState = View.WINDOW_MAXIMIZED; - stdout.println("Initialize to full screen"); - } else { - view.move(r[0], r[1], r[2], r[3]); - } - } else { - var w = handler.box(#width, #border) - var h = handler.box(#height, #border) - if (w >= sw || h >= sh) { - view.windowState = View.WINDOW_MAXIMIZED; - return; - } - // extra for border - var extra = 2; - centerize(w + extra, handler.box(#height, #border) + h + extra); - } -} - -function self.closing() { - var (x, y, w, h) = view.box(#rectw, #border, #screen); - if (is_file_transfer) save_file_transfer_close_state(); - if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h); -} - -handler.setPermission = function(name, enabled) { - if (name == "keyboard") keyboard_enabled = enabled; - if (name == "audio") audio_enabled = enabled; - if (name == "clipboard") clipboard_enabled = enabled; - header.update(); -} - -handler.closeSuccess = function() { - // handler.msgbox("success", "Successful", "Ready to go."); - handler.msgbox("", "", ""); -}