├── README.md ├── app ├── apiv1.go ├── filedownload.go ├── filedownload_test.go ├── fileupload.go ├── params.go ├── response.go ├── sandbox.go ├── vncserver.go └── wsProxy.go ├── assets ├── .keep └── novnc │ ├── core │ ├── base64.js │ ├── decoders │ │ ├── copyrect.js │ │ ├── hextile.js │ │ ├── raw.js │ │ ├── rre.js │ │ ├── tight.js │ │ └── tightpng.js │ ├── deflator.js │ ├── des.js │ ├── display.js │ ├── encodings.js │ ├── inflator.js │ ├── input │ │ ├── domkeytable.js │ │ ├── fixedkeys.js │ │ ├── gesturehandler.js │ │ ├── keyboard.js │ │ ├── keysym.js │ │ ├── keysymdef.js │ │ ├── util.js │ │ ├── vkeys.js │ │ └── xtscancodes.js │ ├── rfb.js │ ├── util │ │ ├── browser.js │ │ ├── cursor.js │ │ ├── element.js │ │ ├── events.js │ │ ├── eventtarget.js │ │ ├── int.js │ │ ├── logging.js │ │ └── strings.js │ └── websock.js │ ├── index.html │ └── vendor │ └── pako │ ├── LICENSE │ ├── README.md │ └── lib │ ├── utils │ └── common.js │ └── zlib │ ├── adler32.js │ ├── constants.js │ ├── crc32.js │ ├── deflate.js │ ├── gzheader.js │ ├── inffast.js │ ├── inflate.js │ ├── inftrees.js │ ├── messages.js │ ├── trees.js │ └── zstream.js ├── bin └── .keep ├── docs ├── README.md ├── favicon.ico ├── index.html ├── logo.svg ├── startx └── xsession ├── env └── env.go ├── go.mod ├── main.go └── pkg ├── customexec └── command.go ├── desktop ├── vncopts.go ├── xInit.go ├── xfce.go ├── xset.go ├── xstartup.go └── xvnc.go └── vncpasswd ├── vncpasswd.go └── vncpasswd_test.go /README.md: -------------------------------------------------------------------------------- 1 | ## VPrix-Agent 2 | 3 | `Agent` 是整个虚拟桌面项目的核心程序。 4 | 5 | 它作为Docker容器内启动的第一个程序,有着类似于linux系统的init的作用。 6 | 7 | 它的功能可以分为以下几类。 8 | 9 | ##### 基础功能 10 | 11 | 1. 根据环境变量,启动`xfce`桌面和`xvnc` 12 | 2. novnc的静态文件服务器 13 | 3. 登录权限校验 14 | 4. 虚拟桌面分享 15 | 5. vnc的代理服务器。 16 | 6. 虚拟桌面的上传下载接口 17 | 7. 虚拟桌面的声音输入输出接口 18 | 19 | ##### 扩展功能 20 | 21 | 1. 执行远程命令 22 | 2. 统计运行数据上报。 23 | 24 | 注意,`agen`t需要搭配`vprix-image`项目中的容器镜像使用,这些镜像再编译的时候已经把`agent`编译进了`image`中。 -------------------------------------------------------------------------------- /app/apiv1.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "agent/env" 5 | "github.com/gogf/gf/frame/g" 6 | "github.com/gogf/gf/net/ghttp" 7 | "github.com/gogf/gf/os/gcache" 8 | "github.com/osgochina/dmicro/logger" 9 | ) 10 | 11 | type UserCheck struct { 12 | Username string `json:"username" v:"required#请传入用户账号名称,只包含英文和数字"` // 用户名,必须是以英文字母开头的字符串,可以包含的字符只有英文和数字 13 | Password string `json:"password" v:"password2#请传入正确格式的密码,长度6-18之间,必须包含大小写字母和数字"` // 登录密码 14 | } 15 | 16 | type ControllerApiV1 struct{} 17 | 18 | // Login 登录 19 | func (that *ControllerApiV1) Login(r *ghttp.Request) { 20 | 21 | username := r.GetString("username") 22 | passwd := r.GetString("passwd") 23 | if len(username) <= 0 || len(passwd) <= 0 { 24 | FailJson(true, r, "用户名或密码错误") 25 | return 26 | } 27 | if username != env.UserName() || passwd != env.Password() { 28 | FailJson(true, r, "用户名或密码错误") 29 | return 30 | } 31 | uid := 1000 32 | err := r.Session.Set("username", username) 33 | err = r.Session.Set("uid", uid) 34 | _ = r.Session.Set("isLogin", true) 35 | if err != nil { 36 | FailJson(true, r, err.Error()) 37 | return 38 | } 39 | SusJson(true, r, "登录成功") 40 | } 41 | 42 | // Logout 用户退出登录 43 | func (that *ControllerApiV1) Logout(r *ghttp.Request) { 44 | err := r.Session.Clear() 45 | if err != nil { 46 | FailJson(true, r, "退出失败") 47 | return 48 | } 49 | SusJson(true, r, "退出成功") 50 | } 51 | 52 | // Share 通过分享链接中的token,获取vnc链接信息 53 | func (that *ControllerApiV1) Share(r *ghttp.Request) { 54 | token := r.GetString("token") 55 | if len(token) <= 0 { 56 | FailJson(true, r, "获取token失败") 57 | return 58 | } 59 | data, err := gcache.Get(token) 60 | if err != nil { 61 | logger.Warning(err) 62 | FailJson(true, r, "获取token信息失败") 63 | return 64 | } 65 | if data == nil { 66 | FailJson(true, r, "获取token信息失败") 67 | return 68 | } 69 | shareConnParams, ok := data.(*ShareConnParams) 70 | if !ok { 71 | FailJson(true, r, "获取token信息失败") 72 | return 73 | } 74 | params, err := gcache.Get(shareConnParams.Token) 75 | if err != nil { 76 | logger.Warning(err) 77 | FailJson(true, r, "获取params信息失败") 78 | return 79 | } 80 | if data == nil { 81 | FailJson(true, r, "获取params信息失败") 82 | return 83 | } 84 | p, ok := params.(*VncConnParams) 85 | if !ok { 86 | FailJson(true, r, "获取params信息失败") 87 | return 88 | } 89 | logger.Infof("分享桌面:%v", shareConnParams) 90 | d := g.Map{ 91 | "token": p.Token, 92 | "nickname": shareConnParams.Nickname, 93 | "host": g.Cfg().GetString("base_url"), 94 | "path": "/proxy/v1/websockify", 95 | "passwd": p.VncPasswd, 96 | "rw": shareConnParams.RW, 97 | } 98 | SusJson(true, r, "ok", d) 99 | } 100 | 101 | // Join 加入到实训机 102 | func (that *ControllerApiV1) Join(r *ghttp.Request) { 103 | uid := r.Session.GetInt("uid", 0) 104 | if uid <= 0 { 105 | FailJson(true, r, "请登录") 106 | return 107 | } 108 | userComputerId := r.GetInt("user_computer_id", 0) 109 | if userComputerId <= 0 { 110 | FailJson(true, r, "请选择要登录的实训机") 111 | return 112 | } 113 | token := "" 114 | SusJson(true, r, "ok", g.Map{"token": token, "url": g.Cfg().GetString("base_url") + "/api/v1/manager"}) 115 | } 116 | -------------------------------------------------------------------------------- /app/filedownload.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gogf/gf/frame/g" 6 | "github.com/gogf/gf/net/ghttp" 7 | "github.com/gogf/gf/os/genv" 8 | "github.com/gogf/gf/os/gfile" 9 | "github.com/gogf/gf/text/gstr" 10 | ) 11 | 12 | type FileInfo struct { 13 | Path string `json:"path"` 14 | Name string `json:"name"` 15 | Size int64 `json:"size"` 16 | IsDir bool `json:"isDir"` 17 | } 18 | 19 | // FileList 展示文件列表信息 20 | func (that *ControllerApiV1) FileList(r *ghttp.Request) { 21 | rootDir := fmt.Sprintf("%s/Downloads", genv.Get("HOME", "/home/vprix-user")) 22 | path := r.GetString("path", "/") 23 | path = fmt.Sprintf("%s/%s", rootDir, gstr.TrimLeft(path, "/")) 24 | path = gfile.Abs(path) 25 | if gstr.Pos(path, rootDir) != 0 { 26 | FailJson(true, r, "path not found") 27 | } 28 | // 扫描目录 29 | paths, err := gfile.ScanDirFunc(path, "*", false, func(path string) string { 30 | if gstr.Pos(gfile.Basename(path), ".") == 0 { 31 | return "" 32 | } 33 | return path 34 | }) 35 | if err != nil { 36 | FailJson(true, r, err.Error()) 37 | } 38 | if len(paths) == 0 { 39 | SusJson(true, r, "ok", g.Map{}) 40 | } 41 | // 迭代路径,生成指定的目录 42 | var FileInfos []*FileInfo 43 | for _, p := range paths { 44 | fileInfo := new(FileInfo) 45 | fileInfo.Path = gstr.Replace(p, rootDir, "") 46 | fInfo, e := gfile.Info(p) 47 | if e != nil { 48 | FailJson(true, r, e.Error()) 49 | } 50 | fileInfo.IsDir = fInfo.IsDir() 51 | fileInfo.Name = fInfo.Name() 52 | fileInfo.Size = fInfo.Size() 53 | FileInfos = append(FileInfos, fileInfo) 54 | } 55 | SusJson(true, r, "ok", FileInfos) 56 | } 57 | 58 | // Download 下载文件 59 | func (that *ControllerApiV1) Download(r *ghttp.Request) { 60 | rootDir := fmt.Sprintf("%s/Downloads", genv.Get("HOME", "/home/vprix-user")) 61 | path := r.GetString("path") 62 | path = fmt.Sprintf("%s/%s", rootDir, gstr.TrimLeft(path, "/")) 63 | path = gfile.Abs(path) 64 | if gstr.Pos(path, rootDir) != 0 { 65 | r.Response.WriteHeader(404) 66 | r.Exit() 67 | } 68 | if !gfile.Exists(path) || gfile.IsDir(path) { 69 | r.Response.WriteHeader(404) 70 | r.Exit() 71 | } 72 | 73 | r.Response.ServeFileDownload(path) 74 | } 75 | -------------------------------------------------------------------------------- /app/filedownload_test.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gogf/gf/os/genv" 6 | "github.com/gogf/gf/os/gfile" 7 | "github.com/gogf/gf/test/gtest" 8 | "github.com/gogf/gf/text/gstr" 9 | "testing" 10 | ) 11 | 12 | func TestControllerApiV1_FileList(t *testing.T) { 13 | gtest.C(t, func(t *gtest.T) { 14 | rootDir := fmt.Sprintf("%s/Downloads", genv.Get("HOME", "/home/vprix-user")) 15 | path := "/" 16 | path = fmt.Sprintf("%s/%s", rootDir, gstr.TrimLeft(path, "/")) 17 | path = gfile.Abs(path) 18 | t.Assert(gstr.Pos(path, rootDir), 0) 19 | paths, err := gfile.ScanDirFunc(path, "*", false, func(path string) string { 20 | if gstr.Pos(gfile.Basename(path), ".") == 0 { 21 | return "" 22 | } 23 | return path 24 | }) 25 | 26 | t.Assert(err, nil) 27 | for _, path := range paths { 28 | t.Log(path) 29 | fileInfo, e := gfile.Info(path) 30 | t.Assert(e, nil) 31 | t.Log(fileInfo) 32 | } 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /app/fileupload.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/gogf/gf/crypto/gmd5" 7 | "github.com/gogf/gf/net/ghttp" 8 | "github.com/gogf/gf/os/genv" 9 | "github.com/gogf/gf/os/gfile" 10 | "github.com/gogf/gf/text/gstr" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "strconv" 15 | ) 16 | 17 | // Upload 分片上传文件 18 | func (that *ControllerApiV1) Upload(r *ghttp.Request) { 19 | // 接收,处理数据 20 | fileName := r.GetString("fileName") // 文件名 21 | chunkNumber := r.GetInt("chunkNumber") // 分片编号 22 | currentChunkSize := r.GetInt("currentChunkSize") // 当前分片的长度 23 | //totalChunks := r.GetInt("totalChunks") // 总的分片数量 24 | fileMd5 := r.GetString("identifier") // 整个文件的md5值 25 | if chunkNumber == 0 || currentChunkSize == 0 || len(fileName) <= 0 || len(fileMd5) <= 0 { 26 | FailJson(true, r, "参数有误") 27 | } 28 | // 临时的文件名 29 | tempFileName := fileMd5 + "_" + strconv.Itoa(chunkNumber) + filepath.Ext(fileName) 30 | 31 | //// 上传文件前,需要做一次校验,使用get请求执行该操作 32 | //// 传入文件的基本信息,服务端根据条件返回不同的操作逻辑个前端 33 | //if r.Method == "GET" { 34 | // // 如果文件长度等于单个分片长度,则告诉前端,可以合并该文件了 35 | // if chunkNumber == totalChunks { 36 | // resultInfo.IsUploaded = 0 37 | // resultInfo.Merge = 1 38 | // SusJson(true, r, "ok", resultInfo) 39 | // } 40 | // 41 | // resultInfo.IsUploaded = 0 42 | // resultInfo.Merge = 0 43 | // SusJson(true, r, "ok", resultInfo) 44 | //} 45 | // 保存当前分片 46 | err := saveChunkToLocalFromMultiPartForm(r, "/tmp", tempFileName, currentChunkSize) 47 | if err != nil { 48 | FailJson(true, r, "上传失败", err.Error()) 49 | } 50 | SusJson(true, r, "ok") 51 | return 52 | } 53 | 54 | // Merge 合并分片文件 55 | func (that *ControllerApiV1) Merge(r *ghttp.Request) { 56 | rootDir := fmt.Sprintf("%s/Uploads", genv.Get("HOME", "/home/vprix-user")) 57 | totalChunks := r.GetInt("totalChunks") 58 | fileName := r.GetString("fileName") 59 | fileMd5 := r.GetString("identifier") 60 | if totalChunks == 0 || len(fileName) <= 0 || len(fileMd5) <= 0 { 61 | FailJson(true, r, "参数有误") 62 | } 63 | targetFileName := fmt.Sprintf("%s/%s", rootDir, fileName) 64 | // 限制要上传的文件必须在uploads目录 65 | if gstr.Pos(gfile.Abs(targetFileName), rootDir) != 0 { 66 | FailJson(true, r, "path not found") 67 | } 68 | f, err := os.OpenFile(targetFileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0777) 69 | if err != nil { 70 | FailJson(true, r, fmt.Sprintf("创建合并文件[%s]失败", targetFileName)) 71 | } 72 | var totalSize int64 73 | writer := bufio.NewWriter(f) 74 | for i := 1; i <= totalChunks; i++ { 75 | currentChunkFile := "/tmp/" + fileMd5 + "_" + strconv.Itoa(i) + filepath.Ext(fileName) // 当前的分片名 76 | bytes, err := ioutil.ReadFile(currentChunkFile) 77 | if err != nil { 78 | FailJson(true, r, fmt.Sprintf("读取分片文件[%s]失败,err:%s", currentChunkFile, err.Error())) 79 | } 80 | num, err := writer.Write(bytes) 81 | if err != nil { 82 | FailJson(true, r, fmt.Sprintf("写入分片文件[%s]失败,err:%s", currentChunkFile, err.Error())) 83 | } 84 | totalSize += int64(num) 85 | err = os.Remove(currentChunkFile) 86 | if err != nil { 87 | FailJson(true, r, "合并分片失败", err.Error()) 88 | } 89 | } 90 | err = writer.Flush() 91 | if err != nil { 92 | FailJson(true, r, fmt.Sprintf("写入全部分片失败,%s", err.Error())) 93 | } 94 | // 在重新打开文件之间关闭 95 | _ = f.Close() 96 | // 计算已合并文件的md5 97 | md5Str, err := gmd5.EncryptFile(targetFileName) 98 | if err != nil { 99 | FailJson(true, r, fmt.Sprintf("计算已合并文件的md5[%s]失败,err:%s", targetFileName, err)) 100 | } 101 | if fileMd5 != md5Str { 102 | //_ = gfile.Remove(targetFileName) 103 | //FailJson(true, r, "md5对比失败") 104 | } 105 | SusJson(true, r, "ok") 106 | } 107 | 108 | // 保存文件分片 109 | func saveChunkToLocalFromMultiPartForm(r *ghttp.Request, tempDir, tempFileName string, currentChunkSize int) (err error) { 110 | if !gfile.IsDir(tempDir) { 111 | err = gfile.Mkdir(tempDir) 112 | if err != nil { 113 | return fmt.Errorf("创建临时文件 %s 失败,err:%v", tempDir, err) 114 | } 115 | err = gfile.Chmod(tempDir, 0766) 116 | if err != nil { 117 | return err 118 | } 119 | } 120 | fileHeader := r.Request.MultipartForm.File["file"][0] 121 | if fileHeader == nil { 122 | return fmt.Errorf("fileHeader 为空") 123 | } 124 | file, err := fileHeader.Open() 125 | if err != nil { 126 | return fmt.Errorf("error : %v", err) 127 | } 128 | // 创建临时文件 129 | myFile, err := gfile.Create(fmt.Sprintf("%s/%s", tempDir, tempFileName)) 130 | if err != nil { 131 | return fmt.Errorf("error : %v", err) 132 | } 133 | // 读取从客户端传过来的文件内容 134 | buf := make([]byte, currentChunkSize) 135 | num, err := file.Read(buf) 136 | if err != nil { 137 | return fmt.Errorf("error : %v", err) 138 | } 139 | if num != currentChunkSize { 140 | return fmt.Errorf("接收的文件长度[%d]与传入的长度[%d]不一致", num, currentChunkSize) 141 | } 142 | num, err = myFile.Write(buf) 143 | if err != nil { 144 | return fmt.Errorf("error : %v", err) 145 | } 146 | // 关闭文件 147 | _ = myFile.Close() 148 | _ = file.Close() 149 | 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /app/params.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/gogf/gf/os/gtime" 4 | 5 | type StartUser struct { 6 | VncPasswd string 7 | Home string 8 | UserName string 9 | GroupName string 10 | UserId uint32 11 | GroupId uint32 12 | } 13 | 14 | type VncConnParams struct { 15 | Host string `json:"host"` 16 | Port int `json:"port"` 17 | Token string `json:"token"` 18 | VncPasswd string `json:"vnc_passwd"` 19 | } 20 | 21 | // ShareConnParams 分享链接的参数 22 | type ShareConnParams struct { 23 | RW string `json:"rw"` 24 | Uid int `json:"uid"` 25 | Nickname string `json:"nickname"` 26 | Token string `json:"token"` 27 | ComputerId int `json:"computer_id"` 28 | ShareTime *gtime.Time `json:"share_time"` 29 | ExpirationTime *gtime.Time `json:"expiration_time"` 30 | } 31 | -------------------------------------------------------------------------------- /app/response.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/gogf/gf/net/ghttp" 5 | ) 6 | 7 | const ( 8 | SuccessCode int = 0 9 | ErrorCode int = -1 10 | ) 11 | 12 | type Response struct { 13 | // 代码 14 | Code int `json:"code" example:"200"` 15 | // 数据集 16 | Data interface{} `json:"data"` 17 | // 消息 18 | Msg string `json:"msg"` 19 | } 20 | 21 | // JsonExit 返回JSON数据并退出当前HTTP执行函数。 22 | func JsonExit(r *ghttp.Request, code int, msg string, data ...interface{}) { 23 | RJson(r, code, msg, data...) 24 | r.Exit() 25 | } 26 | 27 | // RJson 标准返回结果数据结构封装。 28 | // 返回固定数据结构的JSON: 29 | // code: 状态码(200:成功,302跳转,和http请求状态码一至); 30 | // msg: 请求结果信息; 31 | // data: 请求结果,根据不同接口返回结果的数据结构不同; 32 | func RJson(r *ghttp.Request, code int, msg string, data ...interface{}) { 33 | responseData := interface{}(nil) 34 | if len(data) > 0 { 35 | responseData = data[0] 36 | } 37 | result := Response{ 38 | Code: code, 39 | Msg: msg, 40 | Data: responseData, 41 | } 42 | r.SetParam("apiReturnRes", result) 43 | _ = r.Response.WriteJson(result) 44 | } 45 | 46 | // SusJson 成功返回JSON 47 | func SusJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) { 48 | if isExit { 49 | JsonExit(r, SuccessCode, msg, data...) 50 | } 51 | RJson(r, SuccessCode, msg, data...) 52 | } 53 | 54 | // FailJson 失败返回JSON 55 | func FailJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) { 56 | if isExit { 57 | JsonExit(r, ErrorCode, msg, data...) 58 | } 59 | RJson(r, ErrorCode, msg, data...) 60 | } 61 | -------------------------------------------------------------------------------- /app/sandbox.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "agent/env" 5 | "github.com/gobuffalo/packr/v2" 6 | "github.com/gogf/gf/frame/g" 7 | "github.com/gogf/gf/net/ghttp" 8 | "github.com/gogf/gf/os/gcache" 9 | "github.com/osgochina/dmicro/easyservice" 10 | "github.com/osgochina/dmicro/logger" 11 | "github.com/osgochina/dmicro/supervisor/process" 12 | "golang.org/x/net/websocket" 13 | "net/http" 14 | ) 15 | 16 | // SandBoxServer 容器启动后对外提供的管理接口,使用加密传输的rpc协议 17 | type SandBoxServer struct { 18 | id int 19 | name string 20 | service *easyservice.EasyService 21 | svr *ghttp.Server 22 | procManager *process.Manager 23 | vncSvr *vncServer 24 | } 25 | 26 | var _ easyservice.ISandBox = new(SandBoxServer) 27 | var sandbox *SandBoxServer 28 | 29 | // NewSandBoxServer 创建服务沙盒对象 30 | func NewSandBoxServer(svc *easyservice.EasyService) *SandBoxServer { 31 | sandbox = &SandBoxServer{ 32 | id: easyservice.GetNextSandBoxId(), 33 | name: "vprix", 34 | service: svc, 35 | } 36 | sandbox.svr = g.Server("vprix") 37 | sandbox.svr.SetPort(env.VprixPort()) 38 | sandbox.procManager = process.NewManager() 39 | return sandbox 40 | } 41 | func MiddlewareCORS(r *ghttp.Request) { 42 | r.Response.CORSDefault() 43 | r.Middleware.Next() 44 | } 45 | 46 | // Setup 启动服务 47 | func (that *SandBoxServer) Setup() error { 48 | that.svr.Group("/api/v1", func(group *ghttp.RouterGroup) { 49 | group.Middleware(MiddlewareCORS) 50 | group.ALL("/", new(ControllerApiV1)) 51 | }) 52 | that.svr.Group("/websockify", func(group *ghttp.RouterGroup) { 53 | group.Middleware(MiddlewareCORS) 54 | group.ALL("/", func(r *ghttp.Request) { 55 | 56 | //if !r.Session.GetVar("isLogin", false).Bool() { 57 | // logger.Warningf("用户未登录") 58 | // r.Exit() 59 | // return 60 | //} 61 | val, err := gcache.Get(StartWorkSpaceVncKey) 62 | if err != nil { 63 | logger.Error(err) 64 | r.Exit() 65 | return 66 | } 67 | if val == nil { 68 | logger.Warningf("vnc服务未启动") 69 | r.Exit() 70 | return 71 | } 72 | vncConnParams := val.(*VncConnParams) 73 | vncProxy := NewWSVncProxy(vncConnParams) 74 | h := websocket.Handler(vncProxy.Start) 75 | h.ServeHTTP(r.Response.Writer, r.Request) 76 | }) 77 | }) 78 | box := packr.New("novnc", "../assets/novnc") 79 | that.svr.Group("/*", func(group *ghttp.RouterGroup) { 80 | group.GET("/core/", func(r *ghttp.Request) { 81 | http.FileServer(box).ServeHTTP(r.Response.Writer, r.Request) 82 | }) 83 | group.GET("/", func(r *ghttp.Request) { 84 | http.FileServer(box).ServeHTTP(r.Response.Writer, r.Request) 85 | }) 86 | }) 87 | 88 | that.vncSvr = NewVncServer() 89 | go func() { 90 | _, err := that.vncSvr.VncStart(&StartUser{UserName: env.User(), GroupName: env.User(), VncPasswd: env.VncPassword()}) 91 | if err != nil { 92 | logger.Error(err) 93 | } 94 | }() 95 | 96 | return that.svr.Start() 97 | } 98 | 99 | // Shutdown 关闭服务 100 | func (that *SandBoxServer) Shutdown() error { 101 | that.procManager.StopAllProcesses() 102 | return nil 103 | } 104 | 105 | func (that *SandBoxServer) ID() int { 106 | return that.id 107 | } 108 | 109 | func (that *SandBoxServer) Name() string { 110 | return that.name 111 | } 112 | 113 | func (that *SandBoxServer) Service() *easyservice.EasyService { 114 | return that.service 115 | } 116 | 117 | func (that *SandBoxServer) ProcManager() *process.Manager { 118 | return that.procManager 119 | } 120 | -------------------------------------------------------------------------------- /app/vncserver.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "agent/env" 5 | "agent/pkg/customexec" 6 | "agent/pkg/desktop" 7 | "agent/pkg/vncpasswd" 8 | "bytes" 9 | "fmt" 10 | "github.com/gogf/gf/errors/gerror" 11 | "github.com/gogf/gf/os/gcache" 12 | "github.com/gogf/gf/os/genv" 13 | "github.com/gogf/gf/os/gfile" 14 | "github.com/gogf/gf/text/gstr" 15 | "github.com/gogf/gf/util/gconv" 16 | "github.com/gogf/gf/util/grand" 17 | "github.com/osgochina/dmicro/logger" 18 | "github.com/osgochina/dmicro/supervisor/process" 19 | "os" 20 | "time" 21 | ) 22 | 23 | const StartWorkSpaceVncKey = "Start_WorkSpace_Vnc_Key" 24 | 25 | // vnc服务 26 | type vncServer struct { 27 | home string 28 | user string 29 | group string 30 | uid int 31 | gid int 32 | // vnc的链接密码 33 | vncPasswd string 34 | // 链接vnc的端口 35 | vncPort int 36 | //当前计算机名 37 | hostname string 38 | // 显示器编号 39 | displayNumber int 40 | //显示面板 41 | display string 42 | // 权限文件存放地址 "$ENV{HOME}/.Xauthority" 43 | xAuthorityFile string 44 | //桌面名称 45 | desktopName string 46 | // 桌面启动的log 47 | desktopLog string 48 | xInit *desktop.XInit 49 | } 50 | 51 | // NewVncServer 创建vnc服务 52 | func NewVncServer() *vncServer { 53 | hostname, _ := os.Hostname() 54 | return &vncServer{ 55 | hostname: hostname, 56 | } 57 | } 58 | 59 | // VncStart 启动vnc服务 60 | func (that *vncServer) VncStart(user *StartUser) (*VncConnParams, error) { 61 | val, err := gcache.Get(StartWorkSpaceVncKey) 62 | if err != nil { 63 | return nil, err 64 | } 65 | if val != nil { 66 | vncSvr := val.(*VncConnParams) 67 | return vncSvr, nil 68 | } 69 | 70 | if len(user.VncPasswd) <= 0 { 71 | return nil, gerror.New("vnc passwd not found.") 72 | } 73 | 74 | if len(user.Home) <= 0 { 75 | user.Home = env.Home() 76 | } 77 | 78 | if len(user.UserName) <= 0 { 79 | user.UserName = env.User() 80 | } 81 | 82 | if user.UserId == 0 { 83 | user.UserId = 1000 84 | } 85 | if user.GroupId == 0 { 86 | user.GroupId = 1000 87 | } 88 | that.init(user) 89 | err = that.startXInit() 90 | if err != nil { 91 | return nil, err 92 | } 93 | time.Sleep(time.Second * 1) 94 | that.beforeStart() 95 | conn := &VncConnParams{ 96 | VncPasswd: user.VncPasswd, 97 | Host: "127.0.0.1", 98 | Port: 5900, 99 | } 100 | _ = gcache.Set(StartWorkSpaceVncKey, conn, 0) 101 | return conn, nil 102 | } 103 | 104 | // Init 初始化参数 105 | func (that *vncServer) init(user *StartUser) { 106 | that.home = user.Home 107 | that.user = user.UserName 108 | that.group = user.GroupName 109 | that.uid = int(user.UserId) 110 | that.gid = int(user.GroupId) 111 | that.vncPasswd = user.VncPasswd 112 | 113 | that.xAuthorityFile = fmt.Sprintf("%s/.Xauthority", that.home) 114 | that.vncPort = 5900 115 | //桌面名 116 | that.desktopName = fmt.Sprintf("%s:%d-%s", that.hostname, that.displayNumber, that.user) 117 | // 启动桌面的log地址 118 | that.desktopLog = fmt.Sprintf("%s/.vnc/%s:%d.log", that.home, that.hostname, that.displayNumber) 119 | } 120 | 121 | // CreateVncPasswd 创建vnc密码 122 | func (that *vncServer) createVncPasswd(password string) error { 123 | fmt.Println("password:", password) 124 | passwd := vncpasswd.AuthVNCEncode(gconv.Bytes(password)) 125 | path := fmt.Sprintf("%s/.vnc/passwd", that.home) 126 | err := gfile.PutBytes(path, passwd) 127 | if err != nil { 128 | return err 129 | } 130 | osFile, err := gfile.Open(fmt.Sprintf("%s/.vnc", that.home)) 131 | if err != nil { 132 | return err 133 | } 134 | err = osFile.Chown(that.uid, that.gid) 135 | if err != nil { 136 | return err 137 | } 138 | return nil 139 | } 140 | 141 | // 生成随机cookie 142 | func (that *vncServer) mCookie() string { 143 | cmd := customexec.Command("mcookie") 144 | cmd.SetUser(that.user) 145 | cookie, _ := cmd.Output() 146 | if len(cookie) > 0 { 147 | return string(cookie) 148 | } 149 | return grand.S(32) 150 | } 151 | 152 | // CreateXAuth 创建连接X服务器的认证信息。 153 | func (that *vncServer) createXAuth() error { 154 | if gfile.Exists(that.xAuthorityFile) { 155 | return nil 156 | } 157 | mCookie := that.mCookie() 158 | tmpXAuthorityFile := "/tmp/tmpXAuthorityFile" 159 | err := gfile.PutContents(tmpXAuthorityFile, 160 | fmt.Sprintf("add %s:%d . %sadd %s/unix:%d . %s", 161 | that.hostname, that.displayNumber, mCookie, that.hostname, that.displayNumber, mCookie), 162 | ) 163 | if err != nil { 164 | return err 165 | } 166 | _, err = gfile.Create(that.xAuthorityFile) 167 | if err != nil { 168 | return err 169 | } 170 | // 修改文件的权限 171 | _ = os.Chown(that.xAuthorityFile, that.uid, that.gid) 172 | cmd := customexec.Command("/usr/bin/xauth", "-f", that.xAuthorityFile, "source", tmpXAuthorityFile) 173 | var in bytes.Buffer 174 | cmd.Stdin = &in 175 | cmd.Env = genv.All() 176 | cmd.SetUser(that.user) 177 | err = cmd.Run() 178 | if err != nil { 179 | return err 180 | } 181 | return nil 182 | } 183 | 184 | // 启动桌面进程 185 | //func (that *vncServer) startDesktop() (err error) { 186 | // xfce := desktop.NewXfce() 187 | // xfce.User = that.user 188 | // xfce.Home = that.home 189 | // xfce.DisplayNumber = that.displayNumber 190 | // xfce.Hostname = that.hostname 191 | // xfce.DesktopName = that.desktopName 192 | // xfce.LogPath = that.desktopLog 193 | // var procEntry *process.ProcEntry 194 | // if env.IsUbuntu() { 195 | // procEntry, err = xfce.XStartupUbuntu() 196 | // } else { 197 | // procEntry, err = xfce.XStartup() 198 | // } 199 | // if err != nil { 200 | // return err 201 | // } 202 | // that.display = xfce.Display 203 | // entry, err := sandbox.ProcManager().NewProcessByEntry(procEntry) 204 | // if err != nil { 205 | // return err 206 | // } 207 | // go entry.Start(true) 208 | // return nil 209 | //} 210 | 211 | // 启动vnc进程 212 | func (that *vncServer) startXInit() error { 213 | // 创建vnc密码 214 | err := that.createVncPasswd(that.vncPasswd) 215 | if err != nil { 216 | logger.Error(err) 217 | return err 218 | } 219 | //创建连接X服务器的认证信息 220 | err = that.createXAuth() 221 | if err != nil { 222 | logger.Error(err) 223 | return err 224 | } 225 | // 启动xvnc 226 | opts := desktop.NewXVncOpts() 227 | opts.Desktop = gstr.Trim(that.desktopName, "'", "\"") 228 | opts.RfbAuth = fmt.Sprintf("%s/.vnc/passwd", that.home) 229 | opts.RfbPort = that.vncPort 230 | that.xInit = desktop.NewXInit(opts) 231 | that.xInit.SetUser(that.user) 232 | that.xInit.SetChroot(that.home) 233 | that.xInit.SetDisplayNumber(that.displayNumber) 234 | that.xInit.SetLogPath(that.desktopLog) 235 | procEntry, err := that.xInit.NewProcess() 236 | if err != nil { 237 | logger.Error(err) 238 | return err 239 | } 240 | entry, err := sandbox.ProcManager().NewProcessByEntry(procEntry) 241 | if err != nil { 242 | logger.Error(err) 243 | return err 244 | } 245 | entry.Start(false) 246 | return nil 247 | } 248 | 249 | // 启动xinit后,执行自定义脚本 250 | func (that *vncServer) beforeStart() { 251 | if !gfile.Exists("/dockerstartup/custom_startup.sh") { 252 | return 253 | } 254 | proc := process.NewProcEntry("/dockerstartup/custom_startup.sh") 255 | proc.SetUser(that.user) 256 | proc.SetAutoReStart("false") 257 | proc.SetRedirectStderr(true) 258 | proc.SetEnvironment(genv.All()) 259 | entry, err := sandbox.ProcManager().NewProcessByEntry(proc) 260 | if err != nil { 261 | logger.Error(err) 262 | return 263 | } 264 | entry.Start(false) 265 | } 266 | -------------------------------------------------------------------------------- /app/wsProxy.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/gogf/gf/os/glog" 5 | "github.com/vprix/vncproxy/rfb" 6 | "github.com/vprix/vncproxy/security" 7 | "github.com/vprix/vncproxy/session" 8 | "github.com/vprix/vncproxy/vnc" 9 | "golang.org/x/net/websocket" 10 | "io" 11 | "net" 12 | "time" 13 | ) 14 | 15 | type WSVncProxy struct { 16 | vncConnParams *VncConnParams 17 | } 18 | 19 | // NewWSVncProxy 生成vnc proxy服务对象 20 | func NewWSVncProxy(vncConnParams *VncConnParams) *WSVncProxy { 21 | vncProxy := &WSVncProxy{ 22 | vncConnParams: vncConnParams, 23 | } 24 | return vncProxy 25 | } 26 | 27 | // Start 启动 28 | func (that *WSVncProxy) Start(ws *websocket.Conn) { 29 | ws.PayloadType = websocket.BinaryFrame 30 | securityHandlers := []rfb.ISecurityHandler{ 31 | &security.ServerAuthNone{}, 32 | } 33 | targetCfg := rfb.TargetConfig{ 34 | Network: "tcp", 35 | Timeout: 10 * time.Second, 36 | Host: that.vncConnParams.Host, 37 | Port: that.vncConnParams.Port, 38 | Password: []byte(that.vncConnParams.VncPasswd), 39 | } 40 | var err error 41 | svrSess := session.NewServerSession( 42 | rfb.OptDesktopName([]byte("Vprix VNC Proxy")), 43 | rfb.OptHeight(768), 44 | rfb.OptWidth(1024), 45 | rfb.OptSecurityHandlers(securityHandlers...), 46 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { 47 | return ws, nil 48 | }), 49 | ) 50 | cliSess := session.NewClient( 51 | rfb.OptSecurityHandlers([]rfb.ISecurityHandler{&security.ClientAuthVNC{Password: targetCfg.Password}}...), 52 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { 53 | return net.DialTimeout(targetCfg.GetNetwork(), targetCfg.Addr(), targetCfg.GetTimeout()) 54 | }), 55 | ) 56 | p := vnc.NewVncProxy(cliSess, svrSess) 57 | err = p.Start() 58 | if err != nil { 59 | glog.Warning(err) 60 | return 61 | } 62 | for { 63 | select { 64 | case err = <-p.Error(): 65 | glog.Warning(err) 66 | return 67 | case <-p.Wait(): 68 | return 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /assets/.keep: -------------------------------------------------------------------------------- 1 | 与存储库一起使用的其他资产(图像、徽标等)。 -------------------------------------------------------------------------------- /assets/novnc/core/base64.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js 6 | 7 | import * as Log from './util/logging.js'; 8 | 9 | export default { 10 | /* Convert data (an array of integers) to a Base64 string. */ 11 | toBase64Table: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''), 12 | base64Pad: '=', 13 | 14 | encode(data) { 15 | "use strict"; 16 | let result = ''; 17 | const length = data.length; 18 | const lengthpad = (length % 3); 19 | // Convert every three bytes to 4 ascii characters. 20 | 21 | for (let i = 0; i < (length - 2); i += 3) { 22 | result += this.toBase64Table[data[i] >> 2]; 23 | result += this.toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)]; 24 | result += this.toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)]; 25 | result += this.toBase64Table[data[i + 2] & 0x3f]; 26 | } 27 | 28 | // Convert the remaining 1 or 2 bytes, pad out to 4 characters. 29 | const j = length - lengthpad; 30 | if (lengthpad === 2) { 31 | result += this.toBase64Table[data[j] >> 2]; 32 | result += this.toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)]; 33 | result += this.toBase64Table[(data[j + 1] & 0x0f) << 2]; 34 | result += this.toBase64Table[64]; 35 | } else if (lengthpad === 1) { 36 | result += this.toBase64Table[data[j] >> 2]; 37 | result += this.toBase64Table[(data[j] & 0x03) << 4]; 38 | result += this.toBase64Table[64]; 39 | result += this.toBase64Table[64]; 40 | } 41 | 42 | return result; 43 | }, 44 | 45 | /* Convert Base64 data to a string */ 46 | /* eslint-disable comma-spacing */ 47 | toBinaryTable: [ 48 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 49 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 50 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, 51 | 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, 52 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 53 | 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, 54 | -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 55 | 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 56 | ], 57 | /* eslint-enable comma-spacing */ 58 | 59 | decode(data, offset = 0) { 60 | let dataLength = data.indexOf('=') - offset; 61 | if (dataLength < 0) { dataLength = data.length - offset; } 62 | 63 | /* Every four characters is 3 resulting numbers */ 64 | const resultLength = (dataLength >> 2) * 3 + Math.floor((dataLength % 4) / 1.5); 65 | const result = new Array(resultLength); 66 | 67 | // Convert one by one. 68 | 69 | let leftbits = 0; // number of bits decoded, but yet to be appended 70 | let leftdata = 0; // bits decoded, but yet to be appended 71 | for (let idx = 0, i = offset; i < data.length; i++) { 72 | const c = this.toBinaryTable[data.charCodeAt(i) & 0x7f]; 73 | const padding = (data.charAt(i) === this.base64Pad); 74 | // Skip illegal characters and whitespace 75 | if (c === -1) { 76 | Log.Error("Illegal character code " + data.charCodeAt(i) + " at position " + i); 77 | continue; 78 | } 79 | 80 | // Collect data into leftdata, update bitcount 81 | leftdata = (leftdata << 6) | c; 82 | leftbits += 6; 83 | 84 | // If we have 8 or more bits, append 8 bits to the result 85 | if (leftbits >= 8) { 86 | leftbits -= 8; 87 | // Append if not padding. 88 | if (!padding) { 89 | result[idx++] = (leftdata >> leftbits) & 0xff; 90 | } 91 | leftdata &= (1 << leftbits) - 1; 92 | } 93 | } 94 | 95 | // If there are any bits left, the base64 string was corrupted 96 | if (leftbits) { 97 | const err = new Error('Corrupted base64 string'); 98 | err.name = 'Base64-Error'; 99 | throw err; 100 | } 101 | 102 | return result; 103 | } 104 | }; /* End of Base64 namespace */ 105 | -------------------------------------------------------------------------------- /assets/novnc/core/decoders/copyrect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | export default class CopyRectDecoder { 11 | decodeRect(x, y, width, height, sock, display, depth) { 12 | if (sock.rQwait("COPYRECT", 4)) { 13 | return false; 14 | } 15 | 16 | let deltaX = sock.rQshift16(); 17 | let deltaY = sock.rQshift16(); 18 | 19 | if ((width === 0) || (height === 0)) { 20 | return true; 21 | } 22 | 23 | display.copyImage(deltaX, deltaY, x, y, width, height); 24 | 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /assets/novnc/core/decoders/hextile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | import * as Log from '../util/logging.js'; 11 | 12 | export default class HextileDecoder { 13 | constructor() { 14 | this._tiles = 0; 15 | this._lastsubencoding = 0; 16 | this._tileBuffer = new Uint8Array(16 * 16 * 4); 17 | } 18 | 19 | decodeRect(x, y, width, height, sock, display, depth) { 20 | if (this._tiles === 0) { 21 | this._tilesX = Math.ceil(width / 16); 22 | this._tilesY = Math.ceil(height / 16); 23 | this._totalTiles = this._tilesX * this._tilesY; 24 | this._tiles = this._totalTiles; 25 | } 26 | 27 | while (this._tiles > 0) { 28 | let bytes = 1; 29 | 30 | if (sock.rQwait("HEXTILE", bytes)) { 31 | return false; 32 | } 33 | 34 | let rQ = sock.rQ; 35 | let rQi = sock.rQi; 36 | 37 | let subencoding = rQ[rQi]; // Peek 38 | if (subencoding > 30) { // Raw 39 | throw new Error("Illegal hextile subencoding (subencoding: " + 40 | subencoding + ")"); 41 | } 42 | 43 | const currTile = this._totalTiles - this._tiles; 44 | const tileX = currTile % this._tilesX; 45 | const tileY = Math.floor(currTile / this._tilesX); 46 | const tx = x + tileX * 16; 47 | const ty = y + tileY * 16; 48 | const tw = Math.min(16, (x + width) - tx); 49 | const th = Math.min(16, (y + height) - ty); 50 | 51 | // Figure out how much we are expecting 52 | if (subencoding & 0x01) { // Raw 53 | bytes += tw * th * 4; 54 | } else { 55 | if (subencoding & 0x02) { // Background 56 | bytes += 4; 57 | } 58 | if (subencoding & 0x04) { // Foreground 59 | bytes += 4; 60 | } 61 | if (subencoding & 0x08) { // AnySubrects 62 | bytes++; // Since we aren't shifting it off 63 | 64 | if (sock.rQwait("HEXTILE", bytes)) { 65 | return false; 66 | } 67 | 68 | let subrects = rQ[rQi + bytes - 1]; // Peek 69 | if (subencoding & 0x10) { // SubrectsColoured 70 | bytes += subrects * (4 + 2); 71 | } else { 72 | bytes += subrects * 2; 73 | } 74 | } 75 | } 76 | 77 | if (sock.rQwait("HEXTILE", bytes)) { 78 | return false; 79 | } 80 | 81 | // We know the encoding and have a whole tile 82 | rQi++; 83 | if (subencoding === 0) { 84 | if (this._lastsubencoding & 0x01) { 85 | // Weird: ignore blanks are RAW 86 | Log.Debug(" Ignoring blank after RAW"); 87 | } else { 88 | display.fillRect(tx, ty, tw, th, this._background); 89 | } 90 | } else if (subencoding & 0x01) { // Raw 91 | let pixels = tw * th; 92 | // Max sure the image is fully opaque 93 | for (let i = 0;i < pixels;i++) { 94 | rQ[rQi + i * 4 + 3] = 255; 95 | } 96 | display.blitImage(tx, ty, tw, th, rQ, rQi); 97 | rQi += bytes - 1; 98 | } else { 99 | if (subencoding & 0x02) { // Background 100 | this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; 101 | rQi += 4; 102 | } 103 | if (subencoding & 0x04) { // Foreground 104 | this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; 105 | rQi += 4; 106 | } 107 | 108 | this._startTile(tx, ty, tw, th, this._background); 109 | if (subencoding & 0x08) { // AnySubrects 110 | let subrects = rQ[rQi]; 111 | rQi++; 112 | 113 | for (let s = 0; s < subrects; s++) { 114 | let color; 115 | if (subencoding & 0x10) { // SubrectsColoured 116 | color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; 117 | rQi += 4; 118 | } else { 119 | color = this._foreground; 120 | } 121 | const xy = rQ[rQi]; 122 | rQi++; 123 | const sx = (xy >> 4); 124 | const sy = (xy & 0x0f); 125 | 126 | const wh = rQ[rQi]; 127 | rQi++; 128 | const sw = (wh >> 4) + 1; 129 | const sh = (wh & 0x0f) + 1; 130 | 131 | this._subTile(sx, sy, sw, sh, color); 132 | } 133 | } 134 | this._finishTile(display); 135 | } 136 | sock.rQi = rQi; 137 | this._lastsubencoding = subencoding; 138 | this._tiles--; 139 | } 140 | 141 | return true; 142 | } 143 | 144 | // start updating a tile 145 | _startTile(x, y, width, height, color) { 146 | this._tileX = x; 147 | this._tileY = y; 148 | this._tileW = width; 149 | this._tileH = height; 150 | 151 | const red = color[0]; 152 | const green = color[1]; 153 | const blue = color[2]; 154 | 155 | const data = this._tileBuffer; 156 | for (let i = 0; i < width * height * 4; i += 4) { 157 | data[i] = red; 158 | data[i + 1] = green; 159 | data[i + 2] = blue; 160 | data[i + 3] = 255; 161 | } 162 | } 163 | 164 | // update sub-rectangle of the current tile 165 | _subTile(x, y, w, h, color) { 166 | const red = color[0]; 167 | const green = color[1]; 168 | const blue = color[2]; 169 | const xend = x + w; 170 | const yend = y + h; 171 | 172 | const data = this._tileBuffer; 173 | const width = this._tileW; 174 | for (let j = y; j < yend; j++) { 175 | for (let i = x; i < xend; i++) { 176 | const p = (i + (j * width)) * 4; 177 | data[p] = red; 178 | data[p + 1] = green; 179 | data[p + 2] = blue; 180 | data[p + 3] = 255; 181 | } 182 | } 183 | } 184 | 185 | // draw the current tile to the screen 186 | _finishTile(display) { 187 | display.blitImage(this._tileX, this._tileY, 188 | this._tileW, this._tileH, 189 | this._tileBuffer, 0); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /assets/novnc/core/decoders/raw.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | export default class RawDecoder { 11 | constructor() { 12 | this._lines = 0; 13 | } 14 | 15 | decodeRect(x, y, width, height, sock, display, depth) { 16 | if ((width === 0) || (height === 0)) { 17 | return true; 18 | } 19 | 20 | if (this._lines === 0) { 21 | this._lines = height; 22 | } 23 | 24 | const pixelSize = depth == 8 ? 1 : 4; 25 | const bytesPerLine = width * pixelSize; 26 | 27 | if (sock.rQwait("RAW", bytesPerLine)) { 28 | return false; 29 | } 30 | 31 | const curY = y + (height - this._lines); 32 | const currHeight = Math.min(this._lines, 33 | Math.floor(sock.rQlen / bytesPerLine)); 34 | const pixels = width * currHeight; 35 | 36 | let data = sock.rQ; 37 | let index = sock.rQi; 38 | 39 | // Convert data if needed 40 | if (depth == 8) { 41 | const newdata = new Uint8Array(pixels * 4); 42 | for (let i = 0; i < pixels; i++) { 43 | newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3; 44 | newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3; 45 | newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3; 46 | newdata[i * 4 + 3] = 255; 47 | } 48 | data = newdata; 49 | index = 0; 50 | } 51 | 52 | // Max sure the image is fully opaque 53 | for (let i = 0; i < pixels; i++) { 54 | data[i * 4 + 3] = 255; 55 | } 56 | 57 | display.blitImage(x, curY, width, currHeight, data, index); 58 | sock.rQskipBytes(currHeight * bytesPerLine); 59 | this._lines -= currHeight; 60 | if (this._lines > 0) { 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /assets/novnc/core/decoders/rre.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | export default class RREDecoder { 11 | constructor() { 12 | this._subrects = 0; 13 | } 14 | 15 | decodeRect(x, y, width, height, sock, display, depth) { 16 | if (this._subrects === 0) { 17 | if (sock.rQwait("RRE", 4 + 4)) { 18 | return false; 19 | } 20 | 21 | this._subrects = sock.rQshift32(); 22 | 23 | let color = sock.rQshiftBytes(4); // Background 24 | display.fillRect(x, y, width, height, color); 25 | } 26 | 27 | while (this._subrects > 0) { 28 | if (sock.rQwait("RRE", 4 + 8)) { 29 | return false; 30 | } 31 | 32 | let color = sock.rQshiftBytes(4); 33 | let sx = sock.rQshift16(); 34 | let sy = sock.rQshift16(); 35 | let swidth = sock.rQshift16(); 36 | let sheight = sock.rQshift16(); 37 | display.fillRect(x + sx, y + sy, swidth, sheight, color); 38 | 39 | this._subrects--; 40 | } 41 | 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /assets/novnc/core/decoders/tight.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca) 5 | * Licensed under MPL 2.0 (see LICENSE.txt) 6 | * 7 | * See README.md for usage and integration instructions. 8 | * 9 | */ 10 | 11 | import * as Log from '../util/logging.js'; 12 | import Inflator from "../inflator.js"; 13 | 14 | export default class TightDecoder { 15 | constructor() { 16 | this._ctl = null; 17 | this._filter = null; 18 | this._numColors = 0; 19 | this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel) 20 | this._len = 0; 21 | 22 | this._zlibs = []; 23 | for (let i = 0; i < 4; i++) { 24 | this._zlibs[i] = new Inflator(); 25 | } 26 | } 27 | 28 | decodeRect(x, y, width, height, sock, display, depth) { 29 | if (this._ctl === null) { 30 | if (sock.rQwait("TIGHT compression-control", 1)) { 31 | return false; 32 | } 33 | 34 | this._ctl = sock.rQshift8(); 35 | 36 | // Reset streams if the server requests it 37 | for (let i = 0; i < 4; i++) { 38 | if ((this._ctl >> i) & 1) { 39 | this._zlibs[i].reset(); 40 | Log.Info("Reset zlib stream " + i); 41 | } 42 | } 43 | 44 | // Figure out filter 45 | this._ctl = this._ctl >> 4; 46 | } 47 | 48 | let ret; 49 | 50 | if (this._ctl === 0x08) { 51 | ret = this._fillRect(x, y, width, height, 52 | sock, display, depth); 53 | } else if (this._ctl === 0x09) { 54 | ret = this._jpegRect(x, y, width, height, 55 | sock, display, depth); 56 | } else if (this._ctl === 0x0A) { 57 | ret = this._pngRect(x, y, width, height, 58 | sock, display, depth); 59 | } else if ((this._ctl & 0x08) == 0) { 60 | ret = this._basicRect(this._ctl, x, y, width, height, 61 | sock, display, depth); 62 | } else { 63 | throw new Error("Illegal tight compression received (ctl: " + 64 | this._ctl + ")"); 65 | } 66 | 67 | if (ret) { 68 | this._ctl = null; 69 | } 70 | 71 | return ret; 72 | } 73 | 74 | _fillRect(x, y, width, height, sock, display, depth) { 75 | if (sock.rQwait("TIGHT", 3)) { 76 | return false; 77 | } 78 | 79 | const rQi = sock.rQi; 80 | const rQ = sock.rQ; 81 | 82 | display.fillRect(x, y, width, height, 83 | [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false); 84 | sock.rQskipBytes(3); 85 | 86 | return true; 87 | } 88 | 89 | _jpegRect(x, y, width, height, sock, display, depth) { 90 | let data = this._readData(sock); 91 | if (data === null) { 92 | return false; 93 | } 94 | 95 | display.imageRect(x, y, width, height, "image/jpeg", data); 96 | 97 | return true; 98 | } 99 | 100 | _pngRect(x, y, width, height, sock, display, depth) { 101 | throw new Error("PNG received in standard Tight rect"); 102 | } 103 | 104 | _basicRect(ctl, x, y, width, height, sock, display, depth) { 105 | if (this._filter === null) { 106 | if (ctl & 0x4) { 107 | if (sock.rQwait("TIGHT", 1)) { 108 | return false; 109 | } 110 | 111 | this._filter = sock.rQshift8(); 112 | } else { 113 | // Implicit CopyFilter 114 | this._filter = 0; 115 | } 116 | } 117 | 118 | let streamId = ctl & 0x3; 119 | 120 | let ret; 121 | 122 | switch (this._filter) { 123 | case 0: // CopyFilter 124 | ret = this._copyFilter(streamId, x, y, width, height, 125 | sock, display, depth); 126 | break; 127 | case 1: // PaletteFilter 128 | ret = this._paletteFilter(streamId, x, y, width, height, 129 | sock, display, depth); 130 | break; 131 | case 2: // GradientFilter 132 | ret = this._gradientFilter(streamId, x, y, width, height, 133 | sock, display, depth); 134 | break; 135 | default: 136 | throw new Error("Illegal tight filter received (ctl: " + 137 | this._filter + ")"); 138 | } 139 | 140 | if (ret) { 141 | this._filter = null; 142 | } 143 | 144 | return ret; 145 | } 146 | 147 | _copyFilter(streamId, x, y, width, height, sock, display, depth) { 148 | const uncompressedSize = width * height * 3; 149 | let data; 150 | 151 | if (uncompressedSize === 0) { 152 | return true; 153 | } 154 | 155 | if (uncompressedSize < 12) { 156 | if (sock.rQwait("TIGHT", uncompressedSize)) { 157 | return false; 158 | } 159 | 160 | data = sock.rQshiftBytes(uncompressedSize); 161 | } else { 162 | data = this._readData(sock); 163 | if (data === null) { 164 | return false; 165 | } 166 | 167 | this._zlibs[streamId].setInput(data); 168 | data = this._zlibs[streamId].inflate(uncompressedSize); 169 | this._zlibs[streamId].setInput(null); 170 | } 171 | 172 | let rgbx = new Uint8Array(width * height * 4); 173 | for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) { 174 | rgbx[i] = data[j]; 175 | rgbx[i + 1] = data[j + 1]; 176 | rgbx[i + 2] = data[j + 2]; 177 | rgbx[i + 3] = 255; // Alpha 178 | } 179 | 180 | display.blitImage(x, y, width, height, rgbx, 0, false); 181 | 182 | return true; 183 | } 184 | 185 | _paletteFilter(streamId, x, y, width, height, sock, display, depth) { 186 | if (this._numColors === 0) { 187 | if (sock.rQwait("TIGHT palette", 1)) { 188 | return false; 189 | } 190 | 191 | const numColors = sock.rQpeek8() + 1; 192 | const paletteSize = numColors * 3; 193 | 194 | if (sock.rQwait("TIGHT palette", 1 + paletteSize)) { 195 | return false; 196 | } 197 | 198 | this._numColors = numColors; 199 | sock.rQskipBytes(1); 200 | 201 | sock.rQshiftTo(this._palette, paletteSize); 202 | } 203 | 204 | const bpp = (this._numColors <= 2) ? 1 : 8; 205 | const rowSize = Math.floor((width * bpp + 7) / 8); 206 | const uncompressedSize = rowSize * height; 207 | 208 | let data; 209 | 210 | if (uncompressedSize === 0) { 211 | return true; 212 | } 213 | 214 | if (uncompressedSize < 12) { 215 | if (sock.rQwait("TIGHT", uncompressedSize)) { 216 | return false; 217 | } 218 | 219 | data = sock.rQshiftBytes(uncompressedSize); 220 | } else { 221 | data = this._readData(sock); 222 | if (data === null) { 223 | return false; 224 | } 225 | 226 | this._zlibs[streamId].setInput(data); 227 | data = this._zlibs[streamId].inflate(uncompressedSize); 228 | this._zlibs[streamId].setInput(null); 229 | } 230 | 231 | // Convert indexed (palette based) image data to RGB 232 | if (this._numColors == 2) { 233 | this._monoRect(x, y, width, height, data, this._palette, display); 234 | } else { 235 | this._paletteRect(x, y, width, height, data, this._palette, display); 236 | } 237 | 238 | this._numColors = 0; 239 | 240 | return true; 241 | } 242 | 243 | _monoRect(x, y, width, height, data, palette, display) { 244 | // Convert indexed (palette based) image data to RGB 245 | // TODO: reduce number of calculations inside loop 246 | const dest = this._getScratchBuffer(width * height * 4); 247 | const w = Math.floor((width + 7) / 8); 248 | const w1 = Math.floor(width / 8); 249 | 250 | for (let y = 0; y < height; y++) { 251 | let dp, sp, x; 252 | for (x = 0; x < w1; x++) { 253 | for (let b = 7; b >= 0; b--) { 254 | dp = (y * width + x * 8 + 7 - b) * 4; 255 | sp = (data[y * w + x] >> b & 1) * 3; 256 | dest[dp] = palette[sp]; 257 | dest[dp + 1] = palette[sp + 1]; 258 | dest[dp + 2] = palette[sp + 2]; 259 | dest[dp + 3] = 255; 260 | } 261 | } 262 | 263 | for (let b = 7; b >= 8 - width % 8; b--) { 264 | dp = (y * width + x * 8 + 7 - b) * 4; 265 | sp = (data[y * w + x] >> b & 1) * 3; 266 | dest[dp] = palette[sp]; 267 | dest[dp + 1] = palette[sp + 1]; 268 | dest[dp + 2] = palette[sp + 2]; 269 | dest[dp + 3] = 255; 270 | } 271 | } 272 | 273 | display.blitImage(x, y, width, height, dest, 0, false); 274 | } 275 | 276 | _paletteRect(x, y, width, height, data, palette, display) { 277 | // Convert indexed (palette based) image data to RGB 278 | const dest = this._getScratchBuffer(width * height * 4); 279 | const total = width * height * 4; 280 | for (let i = 0, j = 0; i < total; i += 4, j++) { 281 | const sp = data[j] * 3; 282 | dest[i] = palette[sp]; 283 | dest[i + 1] = palette[sp + 1]; 284 | dest[i + 2] = palette[sp + 2]; 285 | dest[i + 3] = 255; 286 | } 287 | 288 | display.blitImage(x, y, width, height, dest, 0, false); 289 | } 290 | 291 | _gradientFilter(streamId, x, y, width, height, sock, display, depth) { 292 | throw new Error("Gradient filter not implemented"); 293 | } 294 | 295 | _readData(sock) { 296 | if (this._len === 0) { 297 | if (sock.rQwait("TIGHT", 3)) { 298 | return null; 299 | } 300 | 301 | let byte; 302 | 303 | byte = sock.rQshift8(); 304 | this._len = byte & 0x7f; 305 | if (byte & 0x80) { 306 | byte = sock.rQshift8(); 307 | this._len |= (byte & 0x7f) << 7; 308 | if (byte & 0x80) { 309 | byte = sock.rQshift8(); 310 | this._len |= byte << 14; 311 | } 312 | } 313 | } 314 | 315 | if (sock.rQwait("TIGHT", this._len)) { 316 | return null; 317 | } 318 | 319 | let data = sock.rQshiftBytes(this._len); 320 | this._len = 0; 321 | 322 | return data; 323 | } 324 | 325 | _getScratchBuffer(size) { 326 | if (!this._scratchBuffer || (this._scratchBuffer.length < size)) { 327 | this._scratchBuffer = new Uint8Array(size); 328 | } 329 | return this._scratchBuffer; 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /assets/novnc/core/decoders/tightpng.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | import TightDecoder from './tight.js'; 11 | 12 | export default class TightPNGDecoder extends TightDecoder { 13 | _pngRect(x, y, width, height, sock, display, depth) { 14 | let data = this._readData(sock); 15 | if (data === null) { 16 | return false; 17 | } 18 | 19 | display.imageRect(x, y, width, height, "image/png", data); 20 | 21 | return true; 22 | } 23 | 24 | _basicRect(ctl, x, y, width, height, sock, display, depth) { 25 | throw new Error("BasicCompression received in TightPNG rect"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /assets/novnc/core/deflator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2020 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; 10 | import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; 11 | import ZStream from "../vendor/pako/lib/zlib/zstream.js"; 12 | 13 | export default class Deflator { 14 | constructor() { 15 | this.strm = new ZStream(); 16 | this.chunkSize = 1024 * 10 * 10; 17 | this.outputBuffer = new Uint8Array(this.chunkSize); 18 | this.windowBits = 5; 19 | 20 | deflateInit(this.strm, this.windowBits); 21 | } 22 | 23 | deflate(inData) { 24 | /* eslint-disable camelcase */ 25 | this.strm.input = inData; 26 | this.strm.avail_in = this.strm.input.length; 27 | this.strm.next_in = 0; 28 | this.strm.output = this.outputBuffer; 29 | this.strm.avail_out = this.chunkSize; 30 | this.strm.next_out = 0; 31 | /* eslint-enable camelcase */ 32 | 33 | let lastRet = deflate(this.strm, Z_FULL_FLUSH); 34 | let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 35 | 36 | if (lastRet < 0) { 37 | throw new Error("zlib deflate failed"); 38 | } 39 | 40 | if (this.strm.avail_in > 0) { 41 | // Read chunks until done 42 | 43 | let chunks = [outData]; 44 | let totalLen = outData.length; 45 | do { 46 | /* eslint-disable camelcase */ 47 | this.strm.output = new Uint8Array(this.chunkSize); 48 | this.strm.next_out = 0; 49 | this.strm.avail_out = this.chunkSize; 50 | /* eslint-enable camelcase */ 51 | 52 | lastRet = deflate(this.strm, Z_FULL_FLUSH); 53 | 54 | if (lastRet < 0) { 55 | throw new Error("zlib deflate failed"); 56 | } 57 | 58 | let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 59 | totalLen += chunk.length; 60 | chunks.push(chunk); 61 | } while (this.strm.avail_in > 0); 62 | 63 | // Combine chunks into a single data 64 | 65 | let newData = new Uint8Array(totalLen); 66 | let offset = 0; 67 | 68 | for (let i = 0; i < chunks.length; i++) { 69 | newData.set(chunks[i], offset); 70 | offset += chunks[i].length; 71 | } 72 | 73 | outData = newData; 74 | } 75 | 76 | /* eslint-disable camelcase */ 77 | this.strm.input = null; 78 | this.strm.avail_in = 0; 79 | this.strm.next_in = 0; 80 | /* eslint-enable camelcase */ 81 | 82 | return outData; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /assets/novnc/core/des.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Ported from Flashlight VNC ActionScript implementation: 3 | * http://www.wizhelp.com/flashlight-vnc/ 4 | * 5 | * Full attribution follows: 6 | * 7 | * ------------------------------------------------------------------------- 8 | * 9 | * This DES class has been extracted from package Acme.Crypto for use in VNC. 10 | * The unnecessary odd parity code has been removed. 11 | * 12 | * These changes are: 13 | * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. 14 | * 15 | * This software is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 18 | * 19 | 20 | * DesCipher - the DES encryption method 21 | * 22 | * The meat of this code is by Dave Zimmerman , and is: 23 | * 24 | * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved. 25 | * 26 | * Permission to use, copy, modify, and distribute this software 27 | * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and 28 | * without fee is hereby granted, provided that this copyright notice is kept 29 | * intact. 30 | * 31 | * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY 32 | * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 33 | * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 34 | * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE 35 | * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 36 | * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. 37 | * 38 | * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE 39 | * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE 40 | * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT 41 | * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE 42 | * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE 43 | * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE 44 | * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP 45 | * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR 46 | * HIGH RISK ACTIVITIES. 47 | * 48 | * 49 | * The rest is: 50 | * 51 | * Copyright (C) 1996 by Jef Poskanzer . All rights reserved. 52 | * 53 | * Redistribution and use in source and binary forms, with or without 54 | * modification, are permitted provided that the following conditions 55 | * are met: 56 | * 1. Redistributions of source code must retain the above copyright 57 | * notice, this list of conditions and the following disclaimer. 58 | * 2. Redistributions in binary form must reproduce the above copyright 59 | * notice, this list of conditions and the following disclaimer in the 60 | * documentation and/or other materials provided with the distribution. 61 | * 62 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 63 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 64 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 65 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 66 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 67 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 68 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 69 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 70 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 71 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 72 | * SUCH DAMAGE. 73 | * 74 | * Visit the ACME Labs Java page for up-to-date versions of this and other 75 | * fine Java utilities: http://www.acme.com/java/ 76 | */ 77 | 78 | /* eslint-disable comma-spacing */ 79 | 80 | // Tables, permutations, S-boxes, etc. 81 | const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3, 82 | 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39, 83 | 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ], 84 | totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28]; 85 | 86 | const z = 0x0; 87 | let a,b,c,d,e,f; 88 | a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e; 89 | const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d, 90 | z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z, 91 | a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f, 92 | c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d]; 93 | a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e; 94 | const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d, 95 | a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f, 96 | z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z, 97 | z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e]; 98 | a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e; 99 | const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f, 100 | b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z, 101 | c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d, 102 | b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e]; 103 | a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e; 104 | const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d, 105 | z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f, 106 | b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e, 107 | c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e]; 108 | a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e; 109 | const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z, 110 | a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f, 111 | z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e, 112 | c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d]; 113 | a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e; 114 | const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f, 115 | z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z, 116 | b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z, 117 | a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f]; 118 | a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e; 119 | const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f, 120 | b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e, 121 | b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e, 122 | z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d]; 123 | a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e; 124 | const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d, 125 | c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z, 126 | a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f, 127 | z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e]; 128 | 129 | /* eslint-enable comma-spacing */ 130 | 131 | export default class DES { 132 | constructor(password) { 133 | this.keys = []; 134 | 135 | // Set the key. 136 | const pc1m = [], pcr = [], kn = []; 137 | 138 | for (let j = 0, l = 56; j < 56; ++j, l -= 8) { 139 | l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1 140 | const m = l & 0x7; 141 | pc1m[j] = ((password[l >>> 3] & (1<>> 10; 171 | this.keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6; 172 | ++KnLi; 173 | this.keys[KnLi] = (raw0 & 0x0003f000) << 12; 174 | this.keys[KnLi] |= (raw0 & 0x0000003f) << 16; 175 | this.keys[KnLi] |= (raw1 & 0x0003f000) >>> 4; 176 | this.keys[KnLi] |= (raw1 & 0x0000003f); 177 | ++KnLi; 178 | } 179 | } 180 | 181 | // Encrypt 8 bytes of text 182 | enc8(text) { 183 | const b = text.slice(); 184 | let i = 0, l, r, x; // left, right, accumulator 185 | 186 | // Squash 8 bytes to 2 ints 187 | l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; 188 | r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; 189 | 190 | x = ((l >>> 4) ^ r) & 0x0f0f0f0f; 191 | r ^= x; 192 | l ^= (x << 4); 193 | x = ((l >>> 16) ^ r) & 0x0000ffff; 194 | r ^= x; 195 | l ^= (x << 16); 196 | x = ((r >>> 2) ^ l) & 0x33333333; 197 | l ^= x; 198 | r ^= (x << 2); 199 | x = ((r >>> 8) ^ l) & 0x00ff00ff; 200 | l ^= x; 201 | r ^= (x << 8); 202 | r = (r << 1) | ((r >>> 31) & 1); 203 | x = (l ^ r) & 0xaaaaaaaa; 204 | l ^= x; 205 | r ^= x; 206 | l = (l << 1) | ((l >>> 31) & 1); 207 | 208 | for (let i = 0, keysi = 0; i < 8; ++i) { 209 | x = (r << 28) | (r >>> 4); 210 | x ^= this.keys[keysi++]; 211 | let fval = SP7[x & 0x3f]; 212 | fval |= SP5[(x >>> 8) & 0x3f]; 213 | fval |= SP3[(x >>> 16) & 0x3f]; 214 | fval |= SP1[(x >>> 24) & 0x3f]; 215 | x = r ^ this.keys[keysi++]; 216 | fval |= SP8[x & 0x3f]; 217 | fval |= SP6[(x >>> 8) & 0x3f]; 218 | fval |= SP4[(x >>> 16) & 0x3f]; 219 | fval |= SP2[(x >>> 24) & 0x3f]; 220 | l ^= fval; 221 | x = (l << 28) | (l >>> 4); 222 | x ^= this.keys[keysi++]; 223 | fval = SP7[x & 0x3f]; 224 | fval |= SP5[(x >>> 8) & 0x3f]; 225 | fval |= SP3[(x >>> 16) & 0x3f]; 226 | fval |= SP1[(x >>> 24) & 0x3f]; 227 | x = l ^ this.keys[keysi++]; 228 | fval |= SP8[x & 0x0000003f]; 229 | fval |= SP6[(x >>> 8) & 0x3f]; 230 | fval |= SP4[(x >>> 16) & 0x3f]; 231 | fval |= SP2[(x >>> 24) & 0x3f]; 232 | r ^= fval; 233 | } 234 | 235 | r = (r << 31) | (r >>> 1); 236 | x = (l ^ r) & 0xaaaaaaaa; 237 | l ^= x; 238 | r ^= x; 239 | l = (l << 31) | (l >>> 1); 240 | x = ((l >>> 8) ^ r) & 0x00ff00ff; 241 | r ^= x; 242 | l ^= (x << 8); 243 | x = ((l >>> 2) ^ r) & 0x33333333; 244 | r ^= x; 245 | l ^= (x << 2); 246 | x = ((r >>> 16) ^ l) & 0x0000ffff; 247 | l ^= x; 248 | r ^= (x << 16); 249 | x = ((r >>> 4) ^ l) & 0x0f0f0f0f; 250 | l ^= x; 251 | r ^= (x << 4); 252 | 253 | // Spread ints to bytes 254 | x = [r, l]; 255 | for (i = 0; i < 8; i++) { 256 | b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256; 257 | if (b[i] < 0) { b[i] += 256; } // unsigned 258 | } 259 | return b; 260 | } 261 | 262 | // Encrypt 16 bytes of text using passwd as key 263 | encrypt(t) { 264 | return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16))); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /assets/novnc/core/encodings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | export const encodings = { 10 | encodingRaw: 0, 11 | encodingCopyRect: 1, 12 | encodingRRE: 2, 13 | encodingHextile: 5, 14 | encodingTight: 7, 15 | encodingTightPNG: -260, 16 | 17 | pseudoEncodingQualityLevel9: -23, 18 | pseudoEncodingQualityLevel0: -32, 19 | pseudoEncodingDesktopSize: -223, 20 | pseudoEncodingLastRect: -224, 21 | pseudoEncodingCursor: -239, 22 | pseudoEncodingQEMUExtendedKeyEvent: -258, 23 | pseudoEncodingDesktopName: -307, 24 | pseudoEncodingExtendedDesktopSize: -308, 25 | pseudoEncodingXvp: -309, 26 | pseudoEncodingFence: -312, 27 | pseudoEncodingContinuousUpdates: -313, 28 | pseudoEncodingCompressLevel9: -247, 29 | pseudoEncodingCompressLevel0: -256, 30 | pseudoEncodingVMwareCursor: 0x574d5664, 31 | pseudoEncodingExtendedClipboard: 0xc0a1e5ce 32 | }; 33 | 34 | export function encodingName(num) { 35 | switch (num) { 36 | case encodings.encodingRaw: return "Raw"; 37 | case encodings.encodingCopyRect: return "CopyRect"; 38 | case encodings.encodingRRE: return "RRE"; 39 | case encodings.encodingHextile: return "Hextile"; 40 | case encodings.encodingTight: return "Tight"; 41 | case encodings.encodingTightPNG: return "TightPNG"; 42 | default: return "[unknown encoding " + num + "]"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /assets/novnc/core/inflator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2020 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js"; 10 | import ZStream from "../vendor/pako/lib/zlib/zstream.js"; 11 | 12 | export default class Inflate { 13 | constructor() { 14 | this.strm = new ZStream(); 15 | this.chunkSize = 1024 * 10 * 10; 16 | this.strm.output = new Uint8Array(this.chunkSize); 17 | this.windowBits = 5; 18 | 19 | inflateInit(this.strm, this.windowBits); 20 | } 21 | 22 | setInput(data) { 23 | if (!data) { 24 | //FIXME: flush remaining data. 25 | /* eslint-disable camelcase */ 26 | this.strm.input = null; 27 | this.strm.avail_in = 0; 28 | this.strm.next_in = 0; 29 | } else { 30 | this.strm.input = data; 31 | this.strm.avail_in = this.strm.input.length; 32 | this.strm.next_in = 0; 33 | /* eslint-enable camelcase */ 34 | } 35 | } 36 | 37 | inflate(expected) { 38 | // resize our output buffer if it's too small 39 | // (we could just use multiple chunks, but that would cause an extra 40 | // allocation each time to flatten the chunks) 41 | if (expected > this.chunkSize) { 42 | this.chunkSize = expected; 43 | this.strm.output = new Uint8Array(this.chunkSize); 44 | } 45 | 46 | /* eslint-disable camelcase */ 47 | this.strm.next_out = 0; 48 | this.strm.avail_out = expected; 49 | /* eslint-enable camelcase */ 50 | 51 | let ret = inflate(this.strm, 0); // Flush argument not used. 52 | if (ret < 0) { 53 | throw new Error("zlib inflate failed"); 54 | } 55 | 56 | if (this.strm.next_out != expected) { 57 | throw new Error("Incomplete zlib block"); 58 | } 59 | 60 | return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 61 | } 62 | 63 | reset() { 64 | inflateReset(this.strm); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /assets/novnc/core/input/domkeytable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2018 The noVNC Authors 4 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt) 5 | */ 6 | 7 | import KeyTable from "./keysym.js"; 8 | 9 | /* 10 | * Mapping between HTML key values and VNC/X11 keysyms for "special" 11 | * keys that cannot be handled via their Unicode codepoint. 12 | * 13 | * See https://www.w3.org/TR/uievents-key/ for possible values. 14 | */ 15 | 16 | const DOMKeyTable = {}; 17 | 18 | function addStandard(key, standard) { 19 | if (standard === undefined) throw new Error("Undefined keysym for key \"" + key + "\""); 20 | if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\""); 21 | DOMKeyTable[key] = [standard, standard, standard, standard]; 22 | } 23 | 24 | function addLeftRight(key, left, right) { 25 | if (left === undefined) throw new Error("Undefined keysym for key \"" + key + "\""); 26 | if (right === undefined) throw new Error("Undefined keysym for key \"" + key + "\""); 27 | if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\""); 28 | DOMKeyTable[key] = [left, left, right, left]; 29 | } 30 | 31 | function addNumpad(key, standard, numpad) { 32 | if (standard === undefined) throw new Error("Undefined keysym for key \"" + key + "\""); 33 | if (numpad === undefined) throw new Error("Undefined keysym for key \"" + key + "\""); 34 | if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\""); 35 | DOMKeyTable[key] = [standard, standard, standard, numpad]; 36 | } 37 | 38 | // 3.2. Modifier Keys 39 | 40 | addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R); 41 | addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift); 42 | addStandard("CapsLock", KeyTable.XK_Caps_Lock); 43 | addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R); 44 | // - Fn 45 | // - FnLock 46 | addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R); 47 | addStandard("NumLock", KeyTable.XK_Num_Lock); 48 | addStandard("ScrollLock", KeyTable.XK_Scroll_Lock); 49 | addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R); 50 | // - Symbol 51 | // - SymbolLock 52 | // - Hyper 53 | // - Super 54 | 55 | // 3.3. Whitespace Keys 56 | 57 | addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter); 58 | addStandard("Tab", KeyTable.XK_Tab); 59 | addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space); 60 | 61 | // 3.4. Navigation Keys 62 | 63 | addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down); 64 | addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left); 65 | addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right); 66 | addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up); 67 | addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End); 68 | addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home); 69 | addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next); 70 | addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior); 71 | 72 | // 3.5. Editing Keys 73 | 74 | addStandard("Backspace", KeyTable.XK_BackSpace); 75 | // Browsers send "Clear" for the numpad 5 without NumLock because 76 | // Windows uses VK_Clear for that key. But Unix expects KP_Begin for 77 | // that scenario. 78 | addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin); 79 | addStandard("Copy", KeyTable.XF86XK_Copy); 80 | // - CrSel 81 | addStandard("Cut", KeyTable.XF86XK_Cut); 82 | addNumpad("Delete", KeyTable.XK_Delete, KeyTable.XK_KP_Delete); 83 | // - EraseEof 84 | // - ExSel 85 | addNumpad("Insert", KeyTable.XK_Insert, KeyTable.XK_KP_Insert); 86 | addStandard("Paste", KeyTable.XF86XK_Paste); 87 | addStandard("Redo", KeyTable.XK_Redo); 88 | addStandard("Undo", KeyTable.XK_Undo); 89 | 90 | // 3.6. UI Keys 91 | 92 | // - Accept 93 | // - Again (could just be XK_Redo) 94 | // - Attn 95 | addStandard("Cancel", KeyTable.XK_Cancel); 96 | addStandard("ContextMenu", KeyTable.XK_Menu); 97 | addStandard("Escape", KeyTable.XK_Escape); 98 | addStandard("Execute", KeyTable.XK_Execute); 99 | addStandard("Find", KeyTable.XK_Find); 100 | addStandard("Help", KeyTable.XK_Help); 101 | addStandard("Pause", KeyTable.XK_Pause); 102 | // - Play 103 | // - Props 104 | addStandard("Select", KeyTable.XK_Select); 105 | addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn); 106 | addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut); 107 | 108 | // 3.7. Device Keys 109 | 110 | addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown); 111 | addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp); 112 | addStandard("Eject", KeyTable.XF86XK_Eject); 113 | addStandard("LogOff", KeyTable.XF86XK_LogOff); 114 | addStandard("Power", KeyTable.XF86XK_PowerOff); 115 | addStandard("PowerOff", KeyTable.XF86XK_PowerDown); 116 | addStandard("PrintScreen", KeyTable.XK_Print); 117 | addStandard("Hibernate", KeyTable.XF86XK_Hibernate); 118 | addStandard("Standby", KeyTable.XF86XK_Standby); 119 | addStandard("WakeUp", KeyTable.XF86XK_WakeUp); 120 | 121 | // 3.8. IME and Composition Keys 122 | 123 | addStandard("AllCandidates", KeyTable.XK_MultipleCandidate); 124 | addStandard("Alphanumeric", KeyTable.XK_Eisu_toggle); 125 | addStandard("CodeInput", KeyTable.XK_Codeinput); 126 | addStandard("Compose", KeyTable.XK_Multi_key); 127 | addStandard("Convert", KeyTable.XK_Henkan); 128 | // - Dead 129 | // - FinalMode 130 | addStandard("GroupFirst", KeyTable.XK_ISO_First_Group); 131 | addStandard("GroupLast", KeyTable.XK_ISO_Last_Group); 132 | addStandard("GroupNext", KeyTable.XK_ISO_Next_Group); 133 | addStandard("GroupPrevious", KeyTable.XK_ISO_Prev_Group); 134 | // - ModeChange (XK_Mode_switch is often used for AltGr) 135 | // - NextCandidate 136 | addStandard("NonConvert", KeyTable.XK_Muhenkan); 137 | addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate); 138 | // - Process 139 | addStandard("SingleCandidate", KeyTable.XK_SingleCandidate); 140 | addStandard("HangulMode", KeyTable.XK_Hangul); 141 | addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja); 142 | addStandard("JunjaMode", KeyTable.XK_Hangul_Jeonja); 143 | addStandard("Eisu", KeyTable.XK_Eisu_toggle); 144 | addStandard("Hankaku", KeyTable.XK_Hankaku); 145 | addStandard("Hiragana", KeyTable.XK_Hiragana); 146 | addStandard("HiraganaKatakana", KeyTable.XK_Hiragana_Katakana); 147 | addStandard("KanaMode", KeyTable.XK_Kana_Shift); // could also be _Kana_Lock 148 | addStandard("KanjiMode", KeyTable.XK_Kanji); 149 | addStandard("Katakana", KeyTable.XK_Katakana); 150 | addStandard("Romaji", KeyTable.XK_Romaji); 151 | addStandard("Zenkaku", KeyTable.XK_Zenkaku); 152 | addStandard("ZenkakuHankaku", KeyTable.XK_Zenkaku_Hankaku); 153 | 154 | // 3.9. General-Purpose Function Keys 155 | 156 | addStandard("F1", KeyTable.XK_F1); 157 | addStandard("F2", KeyTable.XK_F2); 158 | addStandard("F3", KeyTable.XK_F3); 159 | addStandard("F4", KeyTable.XK_F4); 160 | addStandard("F5", KeyTable.XK_F5); 161 | addStandard("F6", KeyTable.XK_F6); 162 | addStandard("F7", KeyTable.XK_F7); 163 | addStandard("F8", KeyTable.XK_F8); 164 | addStandard("F9", KeyTable.XK_F9); 165 | addStandard("F10", KeyTable.XK_F10); 166 | addStandard("F11", KeyTable.XK_F11); 167 | addStandard("F12", KeyTable.XK_F12); 168 | addStandard("F13", KeyTable.XK_F13); 169 | addStandard("F14", KeyTable.XK_F14); 170 | addStandard("F15", KeyTable.XK_F15); 171 | addStandard("F16", KeyTable.XK_F16); 172 | addStandard("F17", KeyTable.XK_F17); 173 | addStandard("F18", KeyTable.XK_F18); 174 | addStandard("F19", KeyTable.XK_F19); 175 | addStandard("F20", KeyTable.XK_F20); 176 | addStandard("F21", KeyTable.XK_F21); 177 | addStandard("F22", KeyTable.XK_F22); 178 | addStandard("F23", KeyTable.XK_F23); 179 | addStandard("F24", KeyTable.XK_F24); 180 | addStandard("F25", KeyTable.XK_F25); 181 | addStandard("F26", KeyTable.XK_F26); 182 | addStandard("F27", KeyTable.XK_F27); 183 | addStandard("F28", KeyTable.XK_F28); 184 | addStandard("F29", KeyTable.XK_F29); 185 | addStandard("F30", KeyTable.XK_F30); 186 | addStandard("F31", KeyTable.XK_F31); 187 | addStandard("F32", KeyTable.XK_F32); 188 | addStandard("F33", KeyTable.XK_F33); 189 | addStandard("F34", KeyTable.XK_F34); 190 | addStandard("F35", KeyTable.XK_F35); 191 | // - Soft1... 192 | 193 | // 3.10. Multimedia Keys 194 | 195 | // - ChannelDown 196 | // - ChannelUp 197 | addStandard("Close", KeyTable.XF86XK_Close); 198 | addStandard("MailForward", KeyTable.XF86XK_MailForward); 199 | addStandard("MailReply", KeyTable.XF86XK_Reply); 200 | addStandard("MailSend", KeyTable.XF86XK_Send); 201 | // - MediaClose 202 | addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward); 203 | addStandard("MediaPause", KeyTable.XF86XK_AudioPause); 204 | addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay); 205 | // - MediaPlayPause 206 | addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord); 207 | addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind); 208 | addStandard("MediaStop", KeyTable.XF86XK_AudioStop); 209 | addStandard("MediaTrackNext", KeyTable.XF86XK_AudioNext); 210 | addStandard("MediaTrackPrevious", KeyTable.XF86XK_AudioPrev); 211 | addStandard("New", KeyTable.XF86XK_New); 212 | addStandard("Open", KeyTable.XF86XK_Open); 213 | addStandard("Print", KeyTable.XK_Print); 214 | addStandard("Save", KeyTable.XF86XK_Save); 215 | addStandard("SpellCheck", KeyTable.XF86XK_Spell); 216 | 217 | // 3.11. Multimedia Numpad Keys 218 | 219 | // - Key11 220 | // - Key12 221 | 222 | // 3.12. Audio Keys 223 | 224 | // - AudioBalanceLeft 225 | // - AudioBalanceRight 226 | // - AudioBassBoostDown 227 | // - AudioBassBoostToggle 228 | // - AudioBassBoostUp 229 | // - AudioFaderFront 230 | // - AudioFaderRear 231 | // - AudioSurroundModeNext 232 | // - AudioTrebleDown 233 | // - AudioTrebleUp 234 | addStandard("AudioVolumeDown", KeyTable.XF86XK_AudioLowerVolume); 235 | addStandard("AudioVolumeUp", KeyTable.XF86XK_AudioRaiseVolume); 236 | addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute); 237 | // - MicrophoneToggle 238 | // - MicrophoneVolumeDown 239 | // - MicrophoneVolumeUp 240 | addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute); 241 | 242 | // 3.13. Speech Keys 243 | 244 | // - SpeechCorrectionList 245 | // - SpeechInputToggle 246 | 247 | // 3.14. Application Keys 248 | 249 | addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer); 250 | addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator); 251 | addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar); 252 | // - LaunchContacts 253 | addStandard("LaunchMail", KeyTable.XF86XK_Mail); 254 | addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia); 255 | addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music); 256 | addStandard("LaunchPhone", KeyTable.XF86XK_Phone); 257 | addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver); 258 | addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel); 259 | addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW); 260 | addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam); 261 | addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word); 262 | 263 | // 3.15. Browser Keys 264 | 265 | addStandard("BrowserBack", KeyTable.XF86XK_Back); 266 | addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites); 267 | addStandard("BrowserForward", KeyTable.XF86XK_Forward); 268 | addStandard("BrowserHome", KeyTable.XF86XK_HomePage); 269 | addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh); 270 | addStandard("BrowserSearch", KeyTable.XF86XK_Search); 271 | addStandard("BrowserStop", KeyTable.XF86XK_Stop); 272 | 273 | // 3.16. Mobile Phone Keys 274 | 275 | // - A whole bunch... 276 | 277 | // 3.17. TV Keys 278 | 279 | // - A whole bunch... 280 | 281 | // 3.18. Media Controller Keys 282 | 283 | // - A whole bunch... 284 | addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust); 285 | addStandard("MediaAudioTrack", KeyTable.XF86XK_AudioCycleTrack); 286 | addStandard("RandomToggle", KeyTable.XF86XK_AudioRandomPlay); 287 | addStandard("SplitScreenToggle", KeyTable.XF86XK_SplitScreen); 288 | addStandard("Subtitle", KeyTable.XF86XK_Subtitle); 289 | addStandard("VideoModeNext", KeyTable.XF86XK_Next_VMode); 290 | 291 | // Extra: Numpad 292 | 293 | addNumpad("=", KeyTable.XK_equal, KeyTable.XK_KP_Equal); 294 | addNumpad("+", KeyTable.XK_plus, KeyTable.XK_KP_Add); 295 | addNumpad("-", KeyTable.XK_minus, KeyTable.XK_KP_Subtract); 296 | addNumpad("*", KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply); 297 | addNumpad("/", KeyTable.XK_slash, KeyTable.XK_KP_Divide); 298 | addNumpad(".", KeyTable.XK_period, KeyTable.XK_KP_Decimal); 299 | addNumpad(",", KeyTable.XK_comma, KeyTable.XK_KP_Separator); 300 | addNumpad("0", KeyTable.XK_0, KeyTable.XK_KP_0); 301 | addNumpad("1", KeyTable.XK_1, KeyTable.XK_KP_1); 302 | addNumpad("2", KeyTable.XK_2, KeyTable.XK_KP_2); 303 | addNumpad("3", KeyTable.XK_3, KeyTable.XK_KP_3); 304 | addNumpad("4", KeyTable.XK_4, KeyTable.XK_KP_4); 305 | addNumpad("5", KeyTable.XK_5, KeyTable.XK_KP_5); 306 | addNumpad("6", KeyTable.XK_6, KeyTable.XK_KP_6); 307 | addNumpad("7", KeyTable.XK_7, KeyTable.XK_KP_7); 308 | addNumpad("8", KeyTable.XK_8, KeyTable.XK_KP_8); 309 | addNumpad("9", KeyTable.XK_9, KeyTable.XK_KP_9); 310 | 311 | export default DOMKeyTable; 312 | -------------------------------------------------------------------------------- /assets/novnc/core/input/fixedkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2018 The noVNC Authors 4 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt) 5 | */ 6 | 7 | /* 8 | * Fallback mapping between HTML key codes (physical keys) and 9 | * HTML key values. This only works for keys that don't vary 10 | * between layouts. We also omit those who manage fine by mapping the 11 | * Unicode representation. 12 | * 13 | * See https://www.w3.org/TR/uievents-code/ for possible codes. 14 | * See https://www.w3.org/TR/uievents-key/ for possible values. 15 | */ 16 | 17 | /* eslint-disable key-spacing */ 18 | 19 | export default { 20 | 21 | // 3.1.1.1. Writing System Keys 22 | 23 | 'Backspace': 'Backspace', 24 | 25 | // 3.1.1.2. Functional Keys 26 | 27 | 'AltLeft': 'Alt', 28 | 'AltRight': 'Alt', // This could also be 'AltGraph' 29 | 'CapsLock': 'CapsLock', 30 | 'ContextMenu': 'ContextMenu', 31 | 'ControlLeft': 'Control', 32 | 'ControlRight': 'Control', 33 | 'Enter': 'Enter', 34 | 'MetaLeft': 'Meta', 35 | 'MetaRight': 'Meta', 36 | 'ShiftLeft': 'Shift', 37 | 'ShiftRight': 'Shift', 38 | 'Tab': 'Tab', 39 | // FIXME: Japanese/Korean keys 40 | 41 | // 3.1.2. Control Pad Section 42 | 43 | 'Delete': 'Delete', 44 | 'End': 'End', 45 | 'Help': 'Help', 46 | 'Home': 'Home', 47 | 'Insert': 'Insert', 48 | 'PageDown': 'PageDown', 49 | 'PageUp': 'PageUp', 50 | 51 | // 3.1.3. Arrow Pad Section 52 | 53 | 'ArrowDown': 'ArrowDown', 54 | 'ArrowLeft': 'ArrowLeft', 55 | 'ArrowRight': 'ArrowRight', 56 | 'ArrowUp': 'ArrowUp', 57 | 58 | // 3.1.4. Numpad Section 59 | 60 | 'NumLock': 'NumLock', 61 | 'NumpadBackspace': 'Backspace', 62 | 'NumpadClear': 'Clear', 63 | 64 | // 3.1.5. Function Section 65 | 66 | 'Escape': 'Escape', 67 | 'F1': 'F1', 68 | 'F2': 'F2', 69 | 'F3': 'F3', 70 | 'F4': 'F4', 71 | 'F5': 'F5', 72 | 'F6': 'F6', 73 | 'F7': 'F7', 74 | 'F8': 'F8', 75 | 'F9': 'F9', 76 | 'F10': 'F10', 77 | 'F11': 'F11', 78 | 'F12': 'F12', 79 | 'F13': 'F13', 80 | 'F14': 'F14', 81 | 'F15': 'F15', 82 | 'F16': 'F16', 83 | 'F17': 'F17', 84 | 'F18': 'F18', 85 | 'F19': 'F19', 86 | 'F20': 'F20', 87 | 'F21': 'F21', 88 | 'F22': 'F22', 89 | 'F23': 'F23', 90 | 'F24': 'F24', 91 | 'F25': 'F25', 92 | 'F26': 'F26', 93 | 'F27': 'F27', 94 | 'F28': 'F28', 95 | 'F29': 'F29', 96 | 'F30': 'F30', 97 | 'F31': 'F31', 98 | 'F32': 'F32', 99 | 'F33': 'F33', 100 | 'F34': 'F34', 101 | 'F35': 'F35', 102 | 'PrintScreen': 'PrintScreen', 103 | 'ScrollLock': 'ScrollLock', 104 | 'Pause': 'Pause', 105 | 106 | // 3.1.6. Media Keys 107 | 108 | 'BrowserBack': 'BrowserBack', 109 | 'BrowserFavorites': 'BrowserFavorites', 110 | 'BrowserForward': 'BrowserForward', 111 | 'BrowserHome': 'BrowserHome', 112 | 'BrowserRefresh': 'BrowserRefresh', 113 | 'BrowserSearch': 'BrowserSearch', 114 | 'BrowserStop': 'BrowserStop', 115 | 'Eject': 'Eject', 116 | 'LaunchApp1': 'LaunchMyComputer', 117 | 'LaunchApp2': 'LaunchCalendar', 118 | 'LaunchMail': 'LaunchMail', 119 | 'MediaPlayPause': 'MediaPlay', 120 | 'MediaStop': 'MediaStop', 121 | 'MediaTrackNext': 'MediaTrackNext', 122 | 'MediaTrackPrevious': 'MediaTrackPrevious', 123 | 'Power': 'Power', 124 | 'Sleep': 'Sleep', 125 | 'AudioVolumeDown': 'AudioVolumeDown', 126 | 'AudioVolumeMute': 'AudioVolumeMute', 127 | 'AudioVolumeUp': 'AudioVolumeUp', 128 | 'WakeUp': 'WakeUp', 129 | }; 130 | -------------------------------------------------------------------------------- /assets/novnc/core/input/keyboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt) 5 | */ 6 | 7 | import * as Log from '../util/logging.js'; 8 | import { stopEvent } from '../util/events.js'; 9 | import * as KeyboardUtil from "./util.js"; 10 | import KeyTable from "./keysym.js"; 11 | import * as browser from "../util/browser.js"; 12 | 13 | // 14 | // Keyboard event handler 15 | // 16 | 17 | export default class Keyboard { 18 | constructor(target) { 19 | this._target = target || null; 20 | 21 | this._keyDownList = {}; // List of depressed keys 22 | // (even if they are happy) 23 | this._altGrArmed = false; // Windows AltGr detection 24 | 25 | // keep these here so we can refer to them later 26 | this._eventHandlers = { 27 | 'keyup': this._handleKeyUp.bind(this), 28 | 'keydown': this._handleKeyDown.bind(this), 29 | 'blur': this._allKeysUp.bind(this), 30 | }; 31 | 32 | // ===== EVENT HANDLERS ===== 33 | 34 | this.onkeyevent = () => {}; // Handler for key press/release 35 | } 36 | 37 | // ===== PRIVATE METHODS ===== 38 | 39 | _sendKeyEvent(keysym, code, down) { 40 | if (down) { 41 | this._keyDownList[code] = keysym; 42 | } else { 43 | // Do we really think this key is down? 44 | if (!(code in this._keyDownList)) { 45 | return; 46 | } 47 | delete this._keyDownList[code]; 48 | } 49 | 50 | Log.Debug("onkeyevent " + (down ? "down" : "up") + 51 | ", keysym: " + keysym, ", code: " + code); 52 | this.onkeyevent(keysym, code, down); 53 | } 54 | 55 | _getKeyCode(e) { 56 | const code = KeyboardUtil.getKeycode(e); 57 | if (code !== 'Unidentified') { 58 | return code; 59 | } 60 | 61 | // Unstable, but we don't have anything else to go on 62 | if (e.keyCode) { 63 | // 229 is used for composition events 64 | if (e.keyCode !== 229) { 65 | return 'Platform' + e.keyCode; 66 | } 67 | } 68 | 69 | // A precursor to the final DOM3 standard. Unfortunately it 70 | // is not layout independent, so it is as bad as using keyCode 71 | if (e.keyIdentifier) { 72 | // Non-character key? 73 | if (e.keyIdentifier.substr(0, 2) !== 'U+') { 74 | return e.keyIdentifier; 75 | } 76 | 77 | const codepoint = parseInt(e.keyIdentifier.substr(2), 16); 78 | const char = String.fromCharCode(codepoint).toUpperCase(); 79 | 80 | return 'Platform' + char.charCodeAt(); 81 | } 82 | 83 | return 'Unidentified'; 84 | } 85 | 86 | _handleKeyDown(e) { 87 | const code = this._getKeyCode(e); 88 | let keysym = KeyboardUtil.getKeysym(e); 89 | 90 | // Windows doesn't have a proper AltGr, but handles it using 91 | // fake Ctrl+Alt. However the remote end might not be Windows, 92 | // so we need to merge those in to a single AltGr event. We 93 | // detect this case by seeing the two key events directly after 94 | // each other with a very short time between them (<50ms). 95 | if (this._altGrArmed) { 96 | this._altGrArmed = false; 97 | clearTimeout(this._altGrTimeout); 98 | 99 | if ((code === "AltRight") && 100 | ((e.timeStamp - this._altGrCtrlTime) < 50)) { 101 | // FIXME: We fail to detect this if either Ctrl key is 102 | // first manually pressed as Windows then no 103 | // longer sends the fake Ctrl down event. It 104 | // does however happily send real Ctrl events 105 | // even when AltGr is already down. Some 106 | // browsers detect this for us though and set the 107 | // key to "AltGraph". 108 | keysym = KeyTable.XK_ISO_Level3_Shift; 109 | } else { 110 | this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); 111 | } 112 | } 113 | 114 | // We cannot handle keys we cannot track, but we also need 115 | // to deal with virtual keyboards which omit key info 116 | if (code === 'Unidentified') { 117 | if (keysym) { 118 | // If it's a virtual keyboard then it should be 119 | // sufficient to just send press and release right 120 | // after each other 121 | this._sendKeyEvent(keysym, code, true); 122 | this._sendKeyEvent(keysym, code, false); 123 | } 124 | 125 | stopEvent(e); 126 | return; 127 | } 128 | 129 | // Alt behaves more like AltGraph on macOS, so shuffle the 130 | // keys around a bit to make things more sane for the remote 131 | // server. This method is used by RealVNC and TigerVNC (and 132 | // possibly others). 133 | if (browser.isMac() || browser.isIOS()) { 134 | switch (keysym) { 135 | case KeyTable.XK_Super_L: 136 | keysym = KeyTable.XK_Alt_L; 137 | break; 138 | case KeyTable.XK_Super_R: 139 | keysym = KeyTable.XK_Super_L; 140 | break; 141 | case KeyTable.XK_Alt_L: 142 | keysym = KeyTable.XK_Mode_switch; 143 | break; 144 | case KeyTable.XK_Alt_R: 145 | keysym = KeyTable.XK_ISO_Level3_Shift; 146 | break; 147 | } 148 | } 149 | 150 | // Is this key already pressed? If so, then we must use the 151 | // same keysym or we'll confuse the server 152 | if (code in this._keyDownList) { 153 | keysym = this._keyDownList[code]; 154 | } 155 | 156 | // macOS doesn't send proper key events for modifiers, only 157 | // state change events. That gets extra confusing for CapsLock 158 | // which toggles on each press, but not on release. So pretend 159 | // it was a quick press and release of the button. 160 | if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { 161 | this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); 162 | this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); 163 | stopEvent(e); 164 | return; 165 | } 166 | 167 | // Windows doesn't send proper key releases for a bunch of 168 | // Japanese IM keys so we have to fake the release right away 169 | const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku, 170 | KeyTable.XK_Eisu_toggle, 171 | KeyTable.XK_Katakana, 172 | KeyTable.XK_Hiragana, 173 | KeyTable.XK_Romaji ]; 174 | if (browser.isWindows() && jpBadKeys.includes(keysym)) { 175 | this._sendKeyEvent(keysym, code, true); 176 | this._sendKeyEvent(keysym, code, false); 177 | stopEvent(e); 178 | return; 179 | } 180 | 181 | stopEvent(e); 182 | 183 | // Possible start of AltGr sequence? (see above) 184 | if ((code === "ControlLeft") && browser.isWindows() && 185 | !("ControlLeft" in this._keyDownList)) { 186 | this._altGrArmed = true; 187 | this._altGrTimeout = setTimeout(this._handleAltGrTimeout.bind(this), 100); 188 | this._altGrCtrlTime = e.timeStamp; 189 | return; 190 | } 191 | 192 | this._sendKeyEvent(keysym, code, true); 193 | } 194 | 195 | _handleKeyUp(e) { 196 | stopEvent(e); 197 | 198 | const code = this._getKeyCode(e); 199 | 200 | // We can't get a release in the middle of an AltGr sequence, so 201 | // abort that detection 202 | if (this._altGrArmed) { 203 | this._altGrArmed = false; 204 | clearTimeout(this._altGrTimeout); 205 | this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); 206 | } 207 | 208 | // See comment in _handleKeyDown() 209 | if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { 210 | this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); 211 | this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); 212 | return; 213 | } 214 | 215 | this._sendKeyEvent(this._keyDownList[code], code, false); 216 | 217 | // Windows has a rather nasty bug where it won't send key 218 | // release events for a Shift button if the other Shift is still 219 | // pressed 220 | if (browser.isWindows() && ((code === 'ShiftLeft') || 221 | (code === 'ShiftRight'))) { 222 | if ('ShiftRight' in this._keyDownList) { 223 | this._sendKeyEvent(this._keyDownList['ShiftRight'], 224 | 'ShiftRight', false); 225 | } 226 | if ('ShiftLeft' in this._keyDownList) { 227 | this._sendKeyEvent(this._keyDownList['ShiftLeft'], 228 | 'ShiftLeft', false); 229 | } 230 | } 231 | } 232 | 233 | _handleAltGrTimeout() { 234 | this._altGrArmed = false; 235 | clearTimeout(this._altGrTimeout); 236 | this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); 237 | } 238 | 239 | _allKeysUp() { 240 | Log.Debug(">> Keyboard.allKeysUp"); 241 | for (let code in this._keyDownList) { 242 | this._sendKeyEvent(this._keyDownList[code], code, false); 243 | } 244 | Log.Debug("<< Keyboard.allKeysUp"); 245 | } 246 | 247 | // ===== PUBLIC METHODS ===== 248 | 249 | grab() { 250 | //Log.Debug(">> Keyboard.grab"); 251 | 252 | this._target.addEventListener('keydown', this._eventHandlers.keydown); 253 | this._target.addEventListener('keyup', this._eventHandlers.keyup); 254 | 255 | // Release (key up) if window loses focus 256 | window.addEventListener('blur', this._eventHandlers.blur); 257 | 258 | //Log.Debug("<< Keyboard.grab"); 259 | } 260 | 261 | ungrab() { 262 | //Log.Debug(">> Keyboard.ungrab"); 263 | 264 | this._target.removeEventListener('keydown', this._eventHandlers.keydown); 265 | this._target.removeEventListener('keyup', this._eventHandlers.keyup); 266 | window.removeEventListener('blur', this._eventHandlers.blur); 267 | 268 | // Release (key up) all keys that are in a down state 269 | this._allKeysUp(); 270 | 271 | //Log.Debug(">> Keyboard.ungrab"); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /assets/novnc/core/input/util.js: -------------------------------------------------------------------------------- 1 | import KeyTable from "./keysym.js"; 2 | import keysyms from "./keysymdef.js"; 3 | import vkeys from "./vkeys.js"; 4 | import fixedkeys from "./fixedkeys.js"; 5 | import DOMKeyTable from "./domkeytable.js"; 6 | import * as browser from "../util/browser.js"; 7 | 8 | // Get 'KeyboardEvent.code', handling legacy browsers 9 | export function getKeycode(evt) { 10 | // Are we getting proper key identifiers? 11 | // (unfortunately Firefox and Chrome are crappy here and gives 12 | // us an empty string on some platforms, rather than leaving it 13 | // undefined) 14 | if (evt.code) { 15 | // Mozilla isn't fully in sync with the spec yet 16 | switch (evt.code) { 17 | case 'OSLeft': return 'MetaLeft'; 18 | case 'OSRight': return 'MetaRight'; 19 | } 20 | 21 | return evt.code; 22 | } 23 | 24 | // The de-facto standard is to use Windows Virtual-Key codes 25 | // in the 'keyCode' field for non-printable characters 26 | if (evt.keyCode in vkeys) { 27 | let code = vkeys[evt.keyCode]; 28 | 29 | // macOS has messed up this code for some reason 30 | if (browser.isMac() && (code === 'ContextMenu')) { 31 | code = 'MetaRight'; 32 | } 33 | 34 | // The keyCode doesn't distinguish between left and right 35 | // for the standard modifiers 36 | if (evt.location === 2) { 37 | switch (code) { 38 | case 'ShiftLeft': return 'ShiftRight'; 39 | case 'ControlLeft': return 'ControlRight'; 40 | case 'AltLeft': return 'AltRight'; 41 | } 42 | } 43 | 44 | // Nor a bunch of the numpad keys 45 | if (evt.location === 3) { 46 | switch (code) { 47 | case 'Delete': return 'NumpadDecimal'; 48 | case 'Insert': return 'Numpad0'; 49 | case 'End': return 'Numpad1'; 50 | case 'ArrowDown': return 'Numpad2'; 51 | case 'PageDown': return 'Numpad3'; 52 | case 'ArrowLeft': return 'Numpad4'; 53 | case 'ArrowRight': return 'Numpad6'; 54 | case 'Home': return 'Numpad7'; 55 | case 'ArrowUp': return 'Numpad8'; 56 | case 'PageUp': return 'Numpad9'; 57 | case 'Enter': return 'NumpadEnter'; 58 | } 59 | } 60 | 61 | return code; 62 | } 63 | 64 | return 'Unidentified'; 65 | } 66 | 67 | // Get 'KeyboardEvent.key', handling legacy browsers 68 | export function getKey(evt) { 69 | // Are we getting a proper key value? 70 | if (evt.key !== undefined) { 71 | // Mozilla isn't fully in sync with the spec yet 72 | switch (evt.key) { 73 | case 'OS': return 'Meta'; 74 | case 'LaunchMyComputer': return 'LaunchApplication1'; 75 | case 'LaunchCalculator': return 'LaunchApplication2'; 76 | } 77 | 78 | // iOS leaks some OS names 79 | switch (evt.key) { 80 | case 'UIKeyInputUpArrow': return 'ArrowUp'; 81 | case 'UIKeyInputDownArrow': return 'ArrowDown'; 82 | case 'UIKeyInputLeftArrow': return 'ArrowLeft'; 83 | case 'UIKeyInputRightArrow': return 'ArrowRight'; 84 | case 'UIKeyInputEscape': return 'Escape'; 85 | } 86 | 87 | // Broken behaviour in Chrome 88 | if ((evt.key === '\x00') && (evt.code === 'NumpadDecimal')) { 89 | return 'Delete'; 90 | } 91 | 92 | return evt.key; 93 | } 94 | 95 | // Try to deduce it based on the physical key 96 | const code = getKeycode(evt); 97 | if (code in fixedkeys) { 98 | return fixedkeys[code]; 99 | } 100 | 101 | // If that failed, then see if we have a printable character 102 | if (evt.charCode) { 103 | return String.fromCharCode(evt.charCode); 104 | } 105 | 106 | // At this point we have nothing left to go on 107 | return 'Unidentified'; 108 | } 109 | 110 | // Get the most reliable keysym value we can get from a key event 111 | export function getKeysym(evt) { 112 | const key = getKey(evt); 113 | 114 | if (key === 'Unidentified') { 115 | return null; 116 | } 117 | 118 | // First look up special keys 119 | if (key in DOMKeyTable) { 120 | let location = evt.location; 121 | 122 | // Safari screws up location for the right cmd key 123 | if ((key === 'Meta') && (location === 0)) { 124 | location = 2; 125 | } 126 | 127 | // And for Clear 128 | if ((key === 'Clear') && (location === 3)) { 129 | let code = getKeycode(evt); 130 | if (code === 'NumLock') { 131 | location = 0; 132 | } 133 | } 134 | 135 | if ((location === undefined) || (location > 3)) { 136 | location = 0; 137 | } 138 | 139 | // The original Meta key now gets confused with the Windows key 140 | // https://bugs.chromium.org/p/chromium/issues/detail?id=1020141 141 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1232918 142 | if (key === 'Meta') { 143 | let code = getKeycode(evt); 144 | if (code === 'AltLeft') { 145 | return KeyTable.XK_Meta_L; 146 | } else if (code === 'AltRight') { 147 | return KeyTable.XK_Meta_R; 148 | } 149 | } 150 | 151 | // macOS has Clear instead of NumLock, but the remote system is 152 | // probably not macOS, so lying here is probably best... 153 | if (key === 'Clear') { 154 | let code = getKeycode(evt); 155 | if (code === 'NumLock') { 156 | return KeyTable.XK_Num_Lock; 157 | } 158 | } 159 | 160 | // Windows sends alternating symbols for some keys when using a 161 | // Japanese layout. We have no way of synchronising with the IM 162 | // running on the remote system, so we send some combined keysym 163 | // instead and hope for the best. 164 | if (browser.isWindows()) { 165 | switch (key) { 166 | case 'Zenkaku': 167 | case 'Hankaku': 168 | return KeyTable.XK_Zenkaku_Hankaku; 169 | case 'Romaji': 170 | case 'KanaMode': 171 | return KeyTable.XK_Romaji; 172 | } 173 | } 174 | 175 | return DOMKeyTable[key][location]; 176 | } 177 | 178 | // Now we need to look at the Unicode symbol instead 179 | 180 | // Special key? (FIXME: Should have been caught earlier) 181 | if (key.length !== 1) { 182 | return null; 183 | } 184 | 185 | const codepoint = key.charCodeAt(); 186 | if (codepoint) { 187 | return keysyms.lookup(codepoint); 188 | } 189 | 190 | return null; 191 | } 192 | -------------------------------------------------------------------------------- /assets/novnc/core/input/vkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2018 The noVNC Authors 4 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt) 5 | */ 6 | 7 | /* 8 | * Mapping between Microsoft® Windows® Virtual-Key codes and 9 | * HTML key codes. 10 | */ 11 | 12 | export default { 13 | 0x08: 'Backspace', 14 | 0x09: 'Tab', 15 | 0x0a: 'NumpadClear', 16 | 0x0d: 'Enter', 17 | 0x10: 'ShiftLeft', 18 | 0x11: 'ControlLeft', 19 | 0x12: 'AltLeft', 20 | 0x13: 'Pause', 21 | 0x14: 'CapsLock', 22 | 0x15: 'Lang1', 23 | 0x19: 'Lang2', 24 | 0x1b: 'Escape', 25 | 0x1c: 'Convert', 26 | 0x1d: 'NonConvert', 27 | 0x20: 'Space', 28 | 0x21: 'PageUp', 29 | 0x22: 'PageDown', 30 | 0x23: 'End', 31 | 0x24: 'Home', 32 | 0x25: 'ArrowLeft', 33 | 0x26: 'ArrowUp', 34 | 0x27: 'ArrowRight', 35 | 0x28: 'ArrowDown', 36 | 0x29: 'Select', 37 | 0x2c: 'PrintScreen', 38 | 0x2d: 'Insert', 39 | 0x2e: 'Delete', 40 | 0x2f: 'Help', 41 | 0x30: 'Digit0', 42 | 0x31: 'Digit1', 43 | 0x32: 'Digit2', 44 | 0x33: 'Digit3', 45 | 0x34: 'Digit4', 46 | 0x35: 'Digit5', 47 | 0x36: 'Digit6', 48 | 0x37: 'Digit7', 49 | 0x38: 'Digit8', 50 | 0x39: 'Digit9', 51 | 0x5b: 'MetaLeft', 52 | 0x5c: 'MetaRight', 53 | 0x5d: 'ContextMenu', 54 | 0x5f: 'Sleep', 55 | 0x60: 'Numpad0', 56 | 0x61: 'Numpad1', 57 | 0x62: 'Numpad2', 58 | 0x63: 'Numpad3', 59 | 0x64: 'Numpad4', 60 | 0x65: 'Numpad5', 61 | 0x66: 'Numpad6', 62 | 0x67: 'Numpad7', 63 | 0x68: 'Numpad8', 64 | 0x69: 'Numpad9', 65 | 0x6a: 'NumpadMultiply', 66 | 0x6b: 'NumpadAdd', 67 | 0x6c: 'NumpadDecimal', 68 | 0x6d: 'NumpadSubtract', 69 | 0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows 70 | 0x6f: 'NumpadDivide', 71 | 0x70: 'F1', 72 | 0x71: 'F2', 73 | 0x72: 'F3', 74 | 0x73: 'F4', 75 | 0x74: 'F5', 76 | 0x75: 'F6', 77 | 0x76: 'F7', 78 | 0x77: 'F8', 79 | 0x78: 'F9', 80 | 0x79: 'F10', 81 | 0x7a: 'F11', 82 | 0x7b: 'F12', 83 | 0x7c: 'F13', 84 | 0x7d: 'F14', 85 | 0x7e: 'F15', 86 | 0x7f: 'F16', 87 | 0x80: 'F17', 88 | 0x81: 'F18', 89 | 0x82: 'F19', 90 | 0x83: 'F20', 91 | 0x84: 'F21', 92 | 0x85: 'F22', 93 | 0x86: 'F23', 94 | 0x87: 'F24', 95 | 0x90: 'NumLock', 96 | 0x91: 'ScrollLock', 97 | 0xa6: 'BrowserBack', 98 | 0xa7: 'BrowserForward', 99 | 0xa8: 'BrowserRefresh', 100 | 0xa9: 'BrowserStop', 101 | 0xaa: 'BrowserSearch', 102 | 0xab: 'BrowserFavorites', 103 | 0xac: 'BrowserHome', 104 | 0xad: 'AudioVolumeMute', 105 | 0xae: 'AudioVolumeDown', 106 | 0xaf: 'AudioVolumeUp', 107 | 0xb0: 'MediaTrackNext', 108 | 0xb1: 'MediaTrackPrevious', 109 | 0xb2: 'MediaStop', 110 | 0xb3: 'MediaPlayPause', 111 | 0xb4: 'LaunchMail', 112 | 0xb5: 'MediaSelect', 113 | 0xb6: 'LaunchApp1', 114 | 0xb7: 'LaunchApp2', 115 | 0xe1: 'AltRight', // Only when it is AltGraph 116 | }; 117 | -------------------------------------------------------------------------------- /assets/novnc/core/util/browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | * Browser feature support detection 9 | */ 10 | 11 | import * as Log from './logging.js'; 12 | 13 | // Touch detection 14 | export let isTouchDevice = ('ontouchstart' in document.documentElement) || 15 | // requried for Chrome debugger 16 | (document.ontouchstart !== undefined) || 17 | // required for MS Surface 18 | (navigator.maxTouchPoints > 0) || 19 | (navigator.msMaxTouchPoints > 0); 20 | window.addEventListener('touchstart', function onFirstTouch() { 21 | isTouchDevice = true; 22 | window.removeEventListener('touchstart', onFirstTouch, false); 23 | }, false); 24 | 25 | 26 | // The goal is to find a certain physical width, the devicePixelRatio 27 | // brings us a bit closer but is not optimal. 28 | export let dragThreshold = 10 * (window.devicePixelRatio || 1); 29 | 30 | let _supportsCursorURIs = false; 31 | 32 | try { 33 | const target = document.createElement('canvas'); 34 | target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default'; 35 | 36 | if (target.style.cursor.indexOf("url") === 0) { 37 | Log.Info("Data URI scheme cursor supported"); 38 | _supportsCursorURIs = true; 39 | } else { 40 | Log.Warn("Data URI scheme cursor not supported"); 41 | } 42 | } catch (exc) { 43 | Log.Error("Data URI scheme cursor test exception: " + exc); 44 | } 45 | 46 | export const supportsCursorURIs = _supportsCursorURIs; 47 | 48 | let _hasScrollbarGutter = true; 49 | try { 50 | // Create invisible container 51 | const container = document.createElement('div'); 52 | container.style.visibility = 'hidden'; 53 | container.style.overflow = 'scroll'; // forcing scrollbars 54 | document.body.appendChild(container); 55 | 56 | // Create a div and place it in the container 57 | const child = document.createElement('div'); 58 | container.appendChild(child); 59 | 60 | // Calculate the difference between the container's full width 61 | // and the child's width - the difference is the scrollbars 62 | const scrollbarWidth = (container.offsetWidth - child.offsetWidth); 63 | 64 | // Clean up 65 | container.parentNode.removeChild(container); 66 | 67 | _hasScrollbarGutter = scrollbarWidth != 0; 68 | } catch (exc) { 69 | Log.Error("Scrollbar test exception: " + exc); 70 | } 71 | export const hasScrollbarGutter = _hasScrollbarGutter; 72 | 73 | /* 74 | * The functions for detection of platforms and browsers below are exported 75 | * but the use of these should be minimized as much as possible. 76 | * 77 | * It's better to use feature detection than platform detection. 78 | */ 79 | 80 | export function isMac() { 81 | return navigator && !!(/mac/i).exec(navigator.platform); 82 | } 83 | 84 | export function isWindows() { 85 | return navigator && !!(/win/i).exec(navigator.platform); 86 | } 87 | 88 | export function isIOS() { 89 | return navigator && 90 | (!!(/ipad/i).exec(navigator.platform) || 91 | !!(/iphone/i).exec(navigator.platform) || 92 | !!(/ipod/i).exec(navigator.platform)); 93 | } 94 | 95 | export function isSafari() { 96 | return navigator && (navigator.userAgent.indexOf('Safari') !== -1 && 97 | navigator.userAgent.indexOf('Chrome') === -1); 98 | } 99 | 100 | export function isFirefox() { 101 | return navigator && !!(/firefox/i).exec(navigator.userAgent); 102 | } 103 | 104 | -------------------------------------------------------------------------------- /assets/novnc/core/util/cursor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt) 5 | */ 6 | 7 | import { supportsCursorURIs, isTouchDevice } from './browser.js'; 8 | 9 | const useFallback = !supportsCursorURIs || isTouchDevice; 10 | 11 | export default class Cursor { 12 | constructor() { 13 | this._target = null; 14 | 15 | this._canvas = document.createElement('canvas'); 16 | 17 | if (useFallback) { 18 | this._canvas.style.position = 'fixed'; 19 | this._canvas.style.zIndex = '65535'; 20 | this._canvas.style.pointerEvents = 'none'; 21 | // Can't use "display" because of Firefox bug #1445997 22 | this._canvas.style.visibility = 'hidden'; 23 | } 24 | 25 | this._position = { x: 0, y: 0 }; 26 | this._hotSpot = { x: 0, y: 0 }; 27 | 28 | this._eventHandlers = { 29 | 'mouseover': this._handleMouseOver.bind(this), 30 | 'mouseleave': this._handleMouseLeave.bind(this), 31 | 'mousemove': this._handleMouseMove.bind(this), 32 | 'mouseup': this._handleMouseUp.bind(this), 33 | }; 34 | } 35 | 36 | attach(target) { 37 | if (this._target) { 38 | this.detach(); 39 | } 40 | 41 | this._target = target; 42 | 43 | if (useFallback) { 44 | document.body.appendChild(this._canvas); 45 | 46 | const options = { capture: true, passive: true }; 47 | this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options); 48 | this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options); 49 | this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options); 50 | this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options); 51 | } 52 | 53 | this.clear(); 54 | } 55 | 56 | detach() { 57 | if (!this._target) { 58 | return; 59 | } 60 | 61 | if (useFallback) { 62 | const options = { capture: true, passive: true }; 63 | this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options); 64 | this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options); 65 | this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options); 66 | this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options); 67 | 68 | document.body.removeChild(this._canvas); 69 | } 70 | 71 | this._target = null; 72 | } 73 | 74 | change(rgba, hotx, hoty, w, h) { 75 | if ((w === 0) || (h === 0)) { 76 | this.clear(); 77 | return; 78 | } 79 | 80 | this._position.x = this._position.x + this._hotSpot.x - hotx; 81 | this._position.y = this._position.y + this._hotSpot.y - hoty; 82 | this._hotSpot.x = hotx; 83 | this._hotSpot.y = hoty; 84 | 85 | let ctx = this._canvas.getContext('2d'); 86 | 87 | this._canvas.width = w; 88 | this._canvas.height = h; 89 | 90 | let img = new ImageData(new Uint8ClampedArray(rgba), w, h); 91 | ctx.clearRect(0, 0, w, h); 92 | ctx.putImageData(img, 0, 0); 93 | 94 | if (useFallback) { 95 | this._updatePosition(); 96 | } else { 97 | let url = this._canvas.toDataURL(); 98 | this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default'; 99 | } 100 | } 101 | 102 | clear() { 103 | this._target.style.cursor = 'none'; 104 | this._canvas.width = 0; 105 | this._canvas.height = 0; 106 | this._position.x = this._position.x + this._hotSpot.x; 107 | this._position.y = this._position.y + this._hotSpot.y; 108 | this._hotSpot.x = 0; 109 | this._hotSpot.y = 0; 110 | } 111 | 112 | // Mouse events might be emulated, this allows 113 | // moving the cursor in such cases 114 | move(clientX, clientY) { 115 | if (!useFallback) { 116 | return; 117 | } 118 | // clientX/clientY are relative the _visual viewport_, 119 | // but our position is relative the _layout viewport_, 120 | // so try to compensate when we can 121 | if (window.visualViewport) { 122 | this._position.x = clientX + window.visualViewport.offsetLeft; 123 | this._position.y = clientY + window.visualViewport.offsetTop; 124 | } else { 125 | this._position.x = clientX; 126 | this._position.y = clientY; 127 | } 128 | this._updatePosition(); 129 | let target = document.elementFromPoint(clientX, clientY); 130 | this._updateVisibility(target); 131 | } 132 | 133 | _handleMouseOver(event) { 134 | // This event could be because we're entering the target, or 135 | // moving around amongst its sub elements. Let the move handler 136 | // sort things out. 137 | this._handleMouseMove(event); 138 | } 139 | 140 | _handleMouseLeave(event) { 141 | // Check if we should show the cursor on the element we are leaving to 142 | this._updateVisibility(event.relatedTarget); 143 | } 144 | 145 | _handleMouseMove(event) { 146 | this._updateVisibility(event.target); 147 | 148 | this._position.x = event.clientX - this._hotSpot.x; 149 | this._position.y = event.clientY - this._hotSpot.y; 150 | 151 | this._updatePosition(); 152 | } 153 | 154 | _handleMouseUp(event) { 155 | // We might get this event because of a drag operation that 156 | // moved outside of the target. Check what's under the cursor 157 | // now and adjust visibility based on that. 158 | let target = document.elementFromPoint(event.clientX, event.clientY); 159 | this._updateVisibility(target); 160 | 161 | // Captures end with a mouseup but we can't know the event order of 162 | // mouseup vs releaseCapture. 163 | // 164 | // In the cases when releaseCapture comes first, the code above is 165 | // enough. 166 | // 167 | // In the cases when the mouseup comes first, we need wait for the 168 | // browser to flush all events and then check again if the cursor 169 | // should be visible. 170 | if (this._captureIsActive()) { 171 | window.setTimeout(() => { 172 | // We might have detached at this point 173 | if (!this._target) { 174 | return; 175 | } 176 | // Refresh the target from elementFromPoint since queued events 177 | // might have altered the DOM 178 | target = document.elementFromPoint(event.clientX, 179 | event.clientY); 180 | this._updateVisibility(target); 181 | }, 0); 182 | } 183 | } 184 | 185 | _showCursor() { 186 | if (this._canvas.style.visibility === 'hidden') { 187 | this._canvas.style.visibility = ''; 188 | } 189 | } 190 | 191 | _hideCursor() { 192 | if (this._canvas.style.visibility !== 'hidden') { 193 | this._canvas.style.visibility = 'hidden'; 194 | } 195 | } 196 | 197 | // Should we currently display the cursor? 198 | // (i.e. are we over the target, or a child of the target without a 199 | // different cursor set) 200 | _shouldShowCursor(target) { 201 | if (!target) { 202 | return false; 203 | } 204 | // Easy case 205 | if (target === this._target) { 206 | return true; 207 | } 208 | // Other part of the DOM? 209 | if (!this._target.contains(target)) { 210 | return false; 211 | } 212 | // Has the child its own cursor? 213 | // FIXME: How can we tell that a sub element has an 214 | // explicit "cursor: none;"? 215 | if (window.getComputedStyle(target).cursor !== 'none') { 216 | return false; 217 | } 218 | return true; 219 | } 220 | 221 | _updateVisibility(target) { 222 | // When the cursor target has capture we want to show the cursor. 223 | // So, if a capture is active - look at the captured element instead. 224 | if (this._captureIsActive()) { 225 | target = document.captureElement; 226 | } 227 | if (this._shouldShowCursor(target)) { 228 | this._showCursor(); 229 | } else { 230 | this._hideCursor(); 231 | } 232 | } 233 | 234 | _updatePosition() { 235 | this._canvas.style.left = this._position.x + "px"; 236 | this._canvas.style.top = this._position.y + "px"; 237 | } 238 | 239 | _captureIsActive() { 240 | return document.captureElement && 241 | document.documentElement.contains(document.captureElement); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /assets/novnc/core/util/element.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2020 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | /* 10 | * HTML element utility functions 11 | */ 12 | 13 | export function clientToElement(x, y, elem) { 14 | const bounds = elem.getBoundingClientRect(); 15 | let pos = { x: 0, y: 0 }; 16 | // Clip to target bounds 17 | if (x < bounds.left) { 18 | pos.x = 0; 19 | } else if (x >= bounds.right) { 20 | pos.x = bounds.width - 1; 21 | } else { 22 | pos.x = x - bounds.left; 23 | } 24 | if (y < bounds.top) { 25 | pos.y = 0; 26 | } else if (y >= bounds.bottom) { 27 | pos.y = bounds.height - 1; 28 | } else { 29 | pos.y = y - bounds.top; 30 | } 31 | return pos; 32 | } 33 | -------------------------------------------------------------------------------- /assets/novnc/core/util/events.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2018 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | /* 10 | * Cross-browser event and position routines 11 | */ 12 | 13 | export function getPointerEvent(e) { 14 | return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e; 15 | } 16 | 17 | export function stopEvent(e) { 18 | e.stopPropagation(); 19 | e.preventDefault(); 20 | } 21 | 22 | // Emulate Element.setCapture() when not supported 23 | let _captureRecursion = false; 24 | let _elementForUnflushedEvents = null; 25 | document.captureElement = null; 26 | function _captureProxy(e) { 27 | // Recursion protection as we'll see our own event 28 | if (_captureRecursion) return; 29 | 30 | // Clone the event as we cannot dispatch an already dispatched event 31 | const newEv = new e.constructor(e.type, e); 32 | 33 | _captureRecursion = true; 34 | if (document.captureElement) { 35 | document.captureElement.dispatchEvent(newEv); 36 | } else { 37 | _elementForUnflushedEvents.dispatchEvent(newEv); 38 | } 39 | _captureRecursion = false; 40 | 41 | // Avoid double events 42 | e.stopPropagation(); 43 | 44 | // Respect the wishes of the redirected event handlers 45 | if (newEv.defaultPrevented) { 46 | e.preventDefault(); 47 | } 48 | 49 | // Implicitly release the capture on button release 50 | if (e.type === "mouseup") { 51 | releaseCapture(); 52 | } 53 | } 54 | 55 | // Follow cursor style of target element 56 | function _capturedElemChanged() { 57 | const proxyElem = document.getElementById("noVNC_mouse_capture_elem"); 58 | proxyElem.style.cursor = window.getComputedStyle(document.captureElement).cursor; 59 | } 60 | 61 | const _captureObserver = new MutationObserver(_capturedElemChanged); 62 | 63 | export function setCapture(target) { 64 | if (target.setCapture) { 65 | 66 | target.setCapture(); 67 | document.captureElement = target; 68 | } else { 69 | // Release any existing capture in case this method is 70 | // called multiple times without coordination 71 | releaseCapture(); 72 | 73 | let proxyElem = document.getElementById("noVNC_mouse_capture_elem"); 74 | 75 | if (proxyElem === null) { 76 | proxyElem = document.createElement("div"); 77 | proxyElem.id = "noVNC_mouse_capture_elem"; 78 | proxyElem.style.position = "fixed"; 79 | proxyElem.style.top = "0px"; 80 | proxyElem.style.left = "0px"; 81 | proxyElem.style.width = "100%"; 82 | proxyElem.style.height = "100%"; 83 | proxyElem.style.zIndex = 10000; 84 | proxyElem.style.display = "none"; 85 | document.body.appendChild(proxyElem); 86 | 87 | // This is to make sure callers don't get confused by having 88 | // our blocking element as the target 89 | proxyElem.addEventListener('contextmenu', _captureProxy); 90 | 91 | proxyElem.addEventListener('mousemove', _captureProxy); 92 | proxyElem.addEventListener('mouseup', _captureProxy); 93 | } 94 | 95 | document.captureElement = target; 96 | 97 | // Track cursor and get initial cursor 98 | _captureObserver.observe(target, {attributes: true}); 99 | _capturedElemChanged(); 100 | 101 | proxyElem.style.display = ""; 102 | 103 | // We listen to events on window in order to keep tracking if it 104 | // happens to leave the viewport 105 | window.addEventListener('mousemove', _captureProxy); 106 | window.addEventListener('mouseup', _captureProxy); 107 | } 108 | } 109 | 110 | export function releaseCapture() { 111 | if (document.releaseCapture) { 112 | 113 | document.releaseCapture(); 114 | document.captureElement = null; 115 | 116 | } else { 117 | if (!document.captureElement) { 118 | return; 119 | } 120 | 121 | // There might be events already queued. The event proxy needs 122 | // access to the captured element for these queued events. 123 | // E.g. contextmenu (right-click) in Microsoft Edge 124 | // 125 | // Before removing the capturedElem pointer we save it to a 126 | // temporary variable that the unflushed events can use. 127 | _elementForUnflushedEvents = document.captureElement; 128 | document.captureElement = null; 129 | 130 | _captureObserver.disconnect(); 131 | 132 | const proxyElem = document.getElementById("noVNC_mouse_capture_elem"); 133 | proxyElem.style.display = "none"; 134 | 135 | window.removeEventListener('mousemove', _captureProxy); 136 | window.removeEventListener('mouseup', _captureProxy); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /assets/novnc/core/util/eventtarget.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | export default class EventTargetMixin { 10 | constructor() { 11 | this._listeners = new Map(); 12 | } 13 | 14 | addEventListener(type, callback) { 15 | if (!this._listeners.has(type)) { 16 | this._listeners.set(type, new Set()); 17 | } 18 | this._listeners.get(type).add(callback); 19 | } 20 | 21 | removeEventListener(type, callback) { 22 | if (this._listeners.has(type)) { 23 | this._listeners.get(type).delete(callback); 24 | } 25 | } 26 | 27 | dispatchEvent(event) { 28 | if (!this._listeners.has(event.type)) { 29 | return true; 30 | } 31 | this._listeners.get(event.type) 32 | .forEach(callback => callback.call(this, event)); 33 | return !event.defaultPrevented; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /assets/novnc/core/util/int.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2020 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | export function toUnsigned32bit(toConvert) { 10 | return toConvert >>> 0; 11 | } 12 | 13 | export function toSigned32bit(toConvert) { 14 | return toConvert | 0; 15 | } 16 | -------------------------------------------------------------------------------- /assets/novnc/core/util/logging.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | /* 10 | * Logging/debug routines 11 | */ 12 | 13 | let _logLevel = 'warn'; 14 | 15 | let Debug = () => {}; 16 | let Info = () => {}; 17 | let Warn = () => {}; 18 | let Error = () => {}; 19 | 20 | export function initLogging(level) { 21 | if (typeof level === 'undefined') { 22 | level = _logLevel; 23 | } else { 24 | _logLevel = level; 25 | } 26 | 27 | Debug = Info = Warn = Error = () => {}; 28 | 29 | if (typeof window.console !== "undefined") { 30 | /* eslint-disable no-console, no-fallthrough */ 31 | switch (level) { 32 | case 'debug': 33 | Debug = console.debug.bind(window.console); 34 | case 'info': 35 | Info = console.info.bind(window.console); 36 | case 'warn': 37 | Warn = console.warn.bind(window.console); 38 | case 'error': 39 | Error = console.error.bind(window.console); 40 | case 'none': 41 | break; 42 | default: 43 | throw new window.Error("invalid logging type '" + level + "'"); 44 | } 45 | /* eslint-enable no-console, no-fallthrough */ 46 | } 47 | } 48 | 49 | export function getLogging() { 50 | return _logLevel; 51 | } 52 | 53 | export { Debug, Info, Warn, Error }; 54 | 55 | // Initialize logging level 56 | initLogging(); 57 | -------------------------------------------------------------------------------- /assets/novnc/core/util/strings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | // Decode from UTF-8 10 | export function decodeUTF8(utf8string, allowLatin1=false) { 11 | try { 12 | return decodeURIComponent(escape(utf8string)); 13 | } catch (e) { 14 | if (e instanceof URIError) { 15 | if (allowLatin1) { 16 | // If we allow Latin1 we can ignore any decoding fails 17 | // and in these cases return the original string 18 | return utf8string; 19 | } 20 | } 21 | throw e; 22 | } 23 | } 24 | 25 | // Encode to UTF-8 26 | export function encodeUTF8(DOMString) { 27 | return unescape(encodeURIComponent(DOMString)); 28 | } 29 | -------------------------------------------------------------------------------- /assets/novnc/core/websock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Websock: high-performance buffering wrapper 3 | * Copyright (C) 2019 The noVNC Authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * Websock is similar to the standard WebSocket / RTCDataChannel object 7 | * but with extra buffer handling. 8 | * 9 | * Websock has built-in receive queue buffering; the message event 10 | * does not contain actual data but is simply a notification that 11 | * there is new data available. Several rQ* methods are available to 12 | * read binary data off of the receive queue. 13 | */ 14 | 15 | import * as Log from './util/logging.js'; 16 | 17 | // this has performance issues in some versions Chromium, and 18 | // doesn't gain a tremendous amount of performance increase in Firefox 19 | // at the moment. It may be valuable to turn it on in the future. 20 | const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB 21 | 22 | // Constants pulled from RTCDataChannelState enum 23 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState#RTCDataChannelState_enum 24 | const DataChannel = { 25 | CONNECTING: "connecting", 26 | OPEN: "open", 27 | CLOSING: "closing", 28 | CLOSED: "closed" 29 | }; 30 | 31 | const ReadyStates = { 32 | CONNECTING: [WebSocket.CONNECTING, DataChannel.CONNECTING], 33 | OPEN: [WebSocket.OPEN, DataChannel.OPEN], 34 | CLOSING: [WebSocket.CLOSING, DataChannel.CLOSING], 35 | CLOSED: [WebSocket.CLOSED, DataChannel.CLOSED], 36 | }; 37 | 38 | // Properties a raw channel must have, WebSocket and RTCDataChannel are two examples 39 | const rawChannelProps = [ 40 | "send", 41 | "close", 42 | "binaryType", 43 | "onerror", 44 | "onmessage", 45 | "onopen", 46 | "protocol", 47 | "readyState", 48 | ]; 49 | 50 | export default class Websock { 51 | constructor() { 52 | this._websocket = null; // WebSocket or RTCDataChannel object 53 | 54 | this._rQi = 0; // Receive queue index 55 | this._rQlen = 0; // Next write position in the receive queue 56 | this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB) 57 | // called in init: this._rQ = new Uint8Array(this._rQbufferSize); 58 | this._rQ = null; // Receive queue 59 | 60 | this._sQbufferSize = 1024 * 10; // 10 KiB 61 | // called in init: this._sQ = new Uint8Array(this._sQbufferSize); 62 | this._sQlen = 0; 63 | this._sQ = null; // Send queue 64 | 65 | this._eventHandlers = { 66 | message: () => {}, 67 | open: () => {}, 68 | close: () => {}, 69 | error: () => {} 70 | }; 71 | } 72 | 73 | // Getters and Setters 74 | 75 | get readyState() { 76 | let subState; 77 | 78 | if (this._websocket === null) { 79 | return "unused"; 80 | } 81 | 82 | subState = this._websocket.readyState; 83 | 84 | if (ReadyStates.CONNECTING.includes(subState)) { 85 | return "connecting"; 86 | } else if (ReadyStates.OPEN.includes(subState)) { 87 | return "open"; 88 | } else if (ReadyStates.CLOSING.includes(subState)) { 89 | return "closing"; 90 | } else if (ReadyStates.CLOSED.includes(subState)) { 91 | return "closed"; 92 | } 93 | 94 | return "unknown"; 95 | } 96 | 97 | get sQ() { 98 | return this._sQ; 99 | } 100 | 101 | get rQ() { 102 | return this._rQ; 103 | } 104 | 105 | get rQi() { 106 | return this._rQi; 107 | } 108 | 109 | set rQi(val) { 110 | this._rQi = val; 111 | } 112 | 113 | // Receive Queue 114 | get rQlen() { 115 | return this._rQlen - this._rQi; 116 | } 117 | 118 | rQpeek8() { 119 | return this._rQ[this._rQi]; 120 | } 121 | 122 | rQskipBytes(bytes) { 123 | this._rQi += bytes; 124 | } 125 | 126 | rQshift8() { 127 | return this._rQshift(1); 128 | } 129 | 130 | rQshift16() { 131 | return this._rQshift(2); 132 | } 133 | 134 | rQshift32() { 135 | return this._rQshift(4); 136 | } 137 | 138 | // TODO(directxman12): test performance with these vs a DataView 139 | _rQshift(bytes) { 140 | let res = 0; 141 | for (let byte = bytes - 1; byte >= 0; byte--) { 142 | res += this._rQ[this._rQi++] << (byte * 8); 143 | } 144 | return res; 145 | } 146 | 147 | rQshiftStr(len) { 148 | if (typeof(len) === 'undefined') { len = this.rQlen; } 149 | let str = ""; 150 | // Handle large arrays in steps to avoid long strings on the stack 151 | for (let i = 0; i < len; i += 4096) { 152 | let part = this.rQshiftBytes(Math.min(4096, len - i)); 153 | str += String.fromCharCode.apply(null, part); 154 | } 155 | return str; 156 | } 157 | 158 | rQshiftBytes(len) { 159 | if (typeof(len) === 'undefined') { len = this.rQlen; } 160 | this._rQi += len; 161 | return new Uint8Array(this._rQ.buffer, this._rQi - len, len); 162 | } 163 | 164 | rQshiftTo(target, len) { 165 | if (len === undefined) { len = this.rQlen; } 166 | // TODO: make this just use set with views when using a ArrayBuffer to store the rQ 167 | target.set(new Uint8Array(this._rQ.buffer, this._rQi, len)); 168 | this._rQi += len; 169 | } 170 | 171 | rQslice(start, end = this.rQlen) { 172 | return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start); 173 | } 174 | 175 | // Check to see if we must wait for 'num' bytes (default to FBU.bytes) 176 | // to be available in the receive queue. Return true if we need to 177 | // wait (and possibly print a debug message), otherwise false. 178 | rQwait(msg, num, goback) { 179 | if (this.rQlen < num) { 180 | if (goback) { 181 | if (this._rQi < goback) { 182 | throw new Error("rQwait cannot backup " + goback + " bytes"); 183 | } 184 | this._rQi -= goback; 185 | } 186 | return true; // true means need more data 187 | } 188 | return false; 189 | } 190 | 191 | // Send Queue 192 | 193 | flush() { 194 | if (this._sQlen > 0 && this.readyState === 'open') { 195 | this._websocket.send(this._encodeMessage()); 196 | this._sQlen = 0; 197 | } 198 | } 199 | 200 | send(arr) { 201 | this._sQ.set(arr, this._sQlen); 202 | this._sQlen += arr.length; 203 | this.flush(); 204 | } 205 | 206 | sendString(str) { 207 | this.send(str.split('').map(chr => chr.charCodeAt(0))); 208 | } 209 | 210 | // Event Handlers 211 | off(evt) { 212 | this._eventHandlers[evt] = () => {}; 213 | } 214 | 215 | on(evt, handler) { 216 | this._eventHandlers[evt] = handler; 217 | } 218 | 219 | _allocateBuffers() { 220 | this._rQ = new Uint8Array(this._rQbufferSize); 221 | this._sQ = new Uint8Array(this._sQbufferSize); 222 | } 223 | 224 | init() { 225 | this._allocateBuffers(); 226 | this._rQi = 0; 227 | this._websocket = null; 228 | } 229 | 230 | open(uri, protocols) { 231 | this.attach(new WebSocket(uri, protocols)); 232 | } 233 | 234 | attach(rawChannel) { 235 | this.init(); 236 | 237 | // Must get object and class methods to be compatible with the tests. 238 | const channelProps = [...Object.keys(rawChannel), ...Object.getOwnPropertyNames(Object.getPrototypeOf(rawChannel))]; 239 | for (let i = 0; i < rawChannelProps.length; i++) { 240 | const prop = rawChannelProps[i]; 241 | if (channelProps.indexOf(prop) < 0) { 242 | throw new Error('Raw channel missing property: ' + prop); 243 | } 244 | } 245 | 246 | this._websocket = rawChannel; 247 | this._websocket.binaryType = "arraybuffer"; 248 | this._websocket.onmessage = this._recvMessage.bind(this); 249 | 250 | this._websocket.onopen = () => { 251 | Log.Debug('>> WebSock.onopen'); 252 | if (this._websocket.protocol) { 253 | Log.Info("Server choose sub-protocol: " + this._websocket.protocol); 254 | } 255 | 256 | this._eventHandlers.open(); 257 | Log.Debug("<< WebSock.onopen"); 258 | }; 259 | 260 | this._websocket.onclose = (e) => { 261 | Log.Debug(">> WebSock.onclose"); 262 | this._eventHandlers.close(e); 263 | Log.Debug("<< WebSock.onclose"); 264 | }; 265 | 266 | this._websocket.onerror = (e) => { 267 | Log.Debug(">> WebSock.onerror: " + e); 268 | this._eventHandlers.error(e); 269 | Log.Debug("<< WebSock.onerror: " + e); 270 | }; 271 | } 272 | 273 | close() { 274 | if (this._websocket) { 275 | if (this.readyState === 'connecting' || 276 | this.readyState === 'open') { 277 | Log.Info("Closing WebSocket connection"); 278 | this._websocket.close(); 279 | } 280 | 281 | this._websocket.onmessage = () => {}; 282 | } 283 | } 284 | 285 | // private methods 286 | _encodeMessage() { 287 | // Put in a binary arraybuffer 288 | // according to the spec, you can send ArrayBufferViews with the send method 289 | return new Uint8Array(this._sQ.buffer, 0, this._sQlen); 290 | } 291 | 292 | // We want to move all the unread data to the start of the queue, 293 | // e.g. compacting. 294 | // The function also expands the receive que if needed, and for 295 | // performance reasons we combine these two actions to avoid 296 | // unneccessary copying. 297 | _expandCompactRQ(minFit) { 298 | // if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place 299 | // instead of resizing 300 | const requiredBufferSize = (this._rQlen - this._rQi + minFit) * 8; 301 | const resizeNeeded = this._rQbufferSize < requiredBufferSize; 302 | 303 | if (resizeNeeded) { 304 | // Make sure we always *at least* double the buffer size, and have at least space for 8x 305 | // the current amount of data 306 | this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize); 307 | } 308 | 309 | // we don't want to grow unboundedly 310 | if (this._rQbufferSize > MAX_RQ_GROW_SIZE) { 311 | this._rQbufferSize = MAX_RQ_GROW_SIZE; 312 | if (this._rQbufferSize - this.rQlen < minFit) { 313 | throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit"); 314 | } 315 | } 316 | 317 | if (resizeNeeded) { 318 | const oldRQbuffer = this._rQ.buffer; 319 | this._rQ = new Uint8Array(this._rQbufferSize); 320 | this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi)); 321 | } else { 322 | this._rQ.copyWithin(0, this._rQi, this._rQlen); 323 | } 324 | 325 | this._rQlen = this._rQlen - this._rQi; 326 | this._rQi = 0; 327 | } 328 | 329 | // push arraybuffer values onto the end of the receive que 330 | _DecodeMessage(data) { 331 | const u8 = new Uint8Array(data); 332 | if (u8.length > this._rQbufferSize - this._rQlen) { 333 | this._expandCompactRQ(u8.length); 334 | } 335 | this._rQ.set(u8, this._rQlen); 336 | this._rQlen += u8.length; 337 | } 338 | 339 | _recvMessage(e) { 340 | this._DecodeMessage(e.data); 341 | if (this.rQlen > 0) { 342 | this._eventHandlers.message(); 343 | if (this._rQlen == this._rQi) { 344 | // All data has now been processed, this means we 345 | // can reset the receive queue. 346 | this._rQlen = 0; 347 | this._rQi = 0; 348 | } 349 | } else { 350 | Log.Debug("Ignoring empty message"); 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /assets/novnc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vprix - 虚拟桌面 5 | 6 | 25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 | 148 | 149 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (C) 2014-2016 by Vitaly Puzrin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/README.md: -------------------------------------------------------------------------------- 1 | This is an ES6-modules-compatible version of 2 | https://github.com/nodeca/pako, based on pako version 1.0.3. 3 | 4 | It's more-or-less a direct translation of the original, with unused parts 5 | removed, and the dynamic support for non-typed arrays removed (since ES6 6 | modules don't work well with dynamic exports). 7 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/lib/utils/common.js: -------------------------------------------------------------------------------- 1 | // reduce buffer size, avoiding mem copy 2 | export function shrinkBuf (buf, size) { 3 | if (buf.length === size) { return buf; } 4 | if (buf.subarray) { return buf.subarray(0, size); } 5 | buf.length = size; 6 | return buf; 7 | }; 8 | 9 | 10 | export function arraySet (dest, src, src_offs, len, dest_offs) { 11 | if (src.subarray && dest.subarray) { 12 | dest.set(src.subarray(src_offs, src_offs + len), dest_offs); 13 | return; 14 | } 15 | // Fallback to ordinary array 16 | for (var i = 0; i < len; i++) { 17 | dest[dest_offs + i] = src[src_offs + i]; 18 | } 19 | } 20 | 21 | // Join array of chunks to single array. 22 | export function flattenChunks (chunks) { 23 | var i, l, len, pos, chunk, result; 24 | 25 | // calculate data length 26 | len = 0; 27 | for (i = 0, l = chunks.length; i < l; i++) { 28 | len += chunks[i].length; 29 | } 30 | 31 | // join chunks 32 | result = new Uint8Array(len); 33 | pos = 0; 34 | for (i = 0, l = chunks.length; i < l; i++) { 35 | chunk = chunks[i]; 36 | result.set(chunk, pos); 37 | pos += chunk.length; 38 | } 39 | 40 | return result; 41 | } 42 | 43 | export var Buf8 = Uint8Array; 44 | export var Buf16 = Uint16Array; 45 | export var Buf32 = Int32Array; 46 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/lib/zlib/adler32.js: -------------------------------------------------------------------------------- 1 | // Note: adler32 takes 12% for level 0 and 2% for level 6. 2 | // It doesn't worth to make additional optimizationa as in original. 3 | // Small size is preferable. 4 | 5 | export default function adler32(adler, buf, len, pos) { 6 | var s1 = (adler & 0xffff) |0, 7 | s2 = ((adler >>> 16) & 0xffff) |0, 8 | n = 0; 9 | 10 | while (len !== 0) { 11 | // Set limit ~ twice less than 5552, to keep 12 | // s2 in 31-bits, because we force signed ints. 13 | // in other case %= will fail. 14 | n = len > 2000 ? 2000 : len; 15 | len -= n; 16 | 17 | do { 18 | s1 = (s1 + buf[pos++]) |0; 19 | s2 = (s2 + s1) |0; 20 | } while (--n); 21 | 22 | s1 %= 65521; 23 | s2 %= 65521; 24 | } 25 | 26 | return (s1 | (s2 << 16)) |0; 27 | } 28 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/lib/zlib/constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | /* Allowed flush values; see deflate() and inflate() below for details */ 4 | Z_NO_FLUSH: 0, 5 | Z_PARTIAL_FLUSH: 1, 6 | Z_SYNC_FLUSH: 2, 7 | Z_FULL_FLUSH: 3, 8 | Z_FINISH: 4, 9 | Z_BLOCK: 5, 10 | Z_TREES: 6, 11 | 12 | /* Return codes for the compression/decompression functions. Negative values 13 | * are errors, positive values are used for special but normal events. 14 | */ 15 | Z_OK: 0, 16 | Z_STREAM_END: 1, 17 | Z_NEED_DICT: 2, 18 | Z_ERRNO: -1, 19 | Z_STREAM_ERROR: -2, 20 | Z_DATA_ERROR: -3, 21 | //Z_MEM_ERROR: -4, 22 | Z_BUF_ERROR: -5, 23 | //Z_VERSION_ERROR: -6, 24 | 25 | /* compression levels */ 26 | Z_NO_COMPRESSION: 0, 27 | Z_BEST_SPEED: 1, 28 | Z_BEST_COMPRESSION: 9, 29 | Z_DEFAULT_COMPRESSION: -1, 30 | 31 | 32 | Z_FILTERED: 1, 33 | Z_HUFFMAN_ONLY: 2, 34 | Z_RLE: 3, 35 | Z_FIXED: 4, 36 | Z_DEFAULT_STRATEGY: 0, 37 | 38 | /* Possible values of the data_type field (though see inflate()) */ 39 | Z_BINARY: 0, 40 | Z_TEXT: 1, 41 | //Z_ASCII: 1, // = Z_TEXT (deprecated) 42 | Z_UNKNOWN: 2, 43 | 44 | /* The deflate compression method */ 45 | Z_DEFLATED: 8 46 | //Z_NULL: null // Use -1 or null inline, depending on var type 47 | }; 48 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/lib/zlib/crc32.js: -------------------------------------------------------------------------------- 1 | // Note: we can't get significant speed boost here. 2 | // So write code to minimize size - no pregenerated tables 3 | // and array tools dependencies. 4 | 5 | 6 | // Use ordinary array, since untyped makes no boost here 7 | export default function makeTable() { 8 | var c, table = []; 9 | 10 | for (var n = 0; n < 256; n++) { 11 | c = n; 12 | for (var k = 0; k < 8; k++) { 13 | c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); 14 | } 15 | table[n] = c; 16 | } 17 | 18 | return table; 19 | } 20 | 21 | // Create table on load. Just 255 signed longs. Not a problem. 22 | var crcTable = makeTable(); 23 | 24 | 25 | function crc32(crc, buf, len, pos) { 26 | var t = crcTable, 27 | end = pos + len; 28 | 29 | crc ^= -1; 30 | 31 | for (var i = pos; i < end; i++) { 32 | crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; 33 | } 34 | 35 | return (crc ^ (-1)); // >>> 0; 36 | } 37 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/lib/zlib/gzheader.js: -------------------------------------------------------------------------------- 1 | export default function GZheader() { 2 | /* true if compressed data believed to be text */ 3 | this.text = 0; 4 | /* modification time */ 5 | this.time = 0; 6 | /* extra flags (not used when writing a gzip file) */ 7 | this.xflags = 0; 8 | /* operating system */ 9 | this.os = 0; 10 | /* pointer to extra field or Z_NULL if none */ 11 | this.extra = null; 12 | /* extra field length (valid if extra != Z_NULL) */ 13 | this.extra_len = 0; // Actually, we don't need it in JS, 14 | // but leave for few code modifications 15 | 16 | // 17 | // Setup limits is not necessary because in js we should not preallocate memory 18 | // for inflate use constant limit in 65536 bytes 19 | // 20 | 21 | /* space at extra (only when reading header) */ 22 | // this.extra_max = 0; 23 | /* pointer to zero-terminated file name or Z_NULL */ 24 | this.name = ''; 25 | /* space at name (only when reading header) */ 26 | // this.name_max = 0; 27 | /* pointer to zero-terminated comment or Z_NULL */ 28 | this.comment = ''; 29 | /* space at comment (only when reading header) */ 30 | // this.comm_max = 0; 31 | /* true if there was or will be a header crc */ 32 | this.hcrc = 0; 33 | /* true when done reading gzip header (not used when writing a gzip file) */ 34 | this.done = false; 35 | } 36 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/lib/zlib/inftrees.js: -------------------------------------------------------------------------------- 1 | import * as utils from "../utils/common.js"; 2 | 3 | var MAXBITS = 15; 4 | var ENOUGH_LENS = 852; 5 | var ENOUGH_DISTS = 592; 6 | //var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); 7 | 8 | var CODES = 0; 9 | var LENS = 1; 10 | var DISTS = 2; 11 | 12 | var lbase = [ /* Length codes 257..285 base */ 13 | 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 14 | 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 15 | ]; 16 | 17 | var lext = [ /* Length codes 257..285 extra */ 18 | 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19 | 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 20 | ]; 21 | 22 | var dbase = [ /* Distance codes 0..29 base */ 23 | 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 24 | 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 25 | 8193, 12289, 16385, 24577, 0, 0 26 | ]; 27 | 28 | var dext = [ /* Distance codes 0..29 extra */ 29 | 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 30 | 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 31 | 28, 28, 29, 29, 64, 64 32 | ]; 33 | 34 | export default function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) 35 | { 36 | var bits = opts.bits; 37 | //here = opts.here; /* table entry for duplication */ 38 | 39 | var len = 0; /* a code's length in bits */ 40 | var sym = 0; /* index of code symbols */ 41 | var min = 0, max = 0; /* minimum and maximum code lengths */ 42 | var root = 0; /* number of index bits for root table */ 43 | var curr = 0; /* number of index bits for current table */ 44 | var drop = 0; /* code bits to drop for sub-table */ 45 | var left = 0; /* number of prefix codes available */ 46 | var used = 0; /* code entries in table used */ 47 | var huff = 0; /* Huffman code */ 48 | var incr; /* for incrementing code, index */ 49 | var fill; /* index for replicating entries */ 50 | var low; /* low bits for current root entry */ 51 | var mask; /* mask for low root bits */ 52 | var next; /* next available space in table */ 53 | var base = null; /* base value table to use */ 54 | var base_index = 0; 55 | // var shoextra; /* extra bits table to use */ 56 | var end; /* use base and extra for symbol > end */ 57 | var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ 58 | var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ 59 | var extra = null; 60 | var extra_index = 0; 61 | 62 | var here_bits, here_op, here_val; 63 | 64 | /* 65 | Process a set of code lengths to create a canonical Huffman code. The 66 | code lengths are lens[0..codes-1]. Each length corresponds to the 67 | symbols 0..codes-1. The Huffman code is generated by first sorting the 68 | symbols by length from short to long, and retaining the symbol order 69 | for codes with equal lengths. Then the code starts with all zero bits 70 | for the first code of the shortest length, and the codes are integer 71 | increments for the same length, and zeros are appended as the length 72 | increases. For the deflate format, these bits are stored backwards 73 | from their more natural integer increment ordering, and so when the 74 | decoding tables are built in the large loop below, the integer codes 75 | are incremented backwards. 76 | 77 | This routine assumes, but does not check, that all of the entries in 78 | lens[] are in the range 0..MAXBITS. The caller must assure this. 79 | 1..MAXBITS is interpreted as that code length. zero means that that 80 | symbol does not occur in this code. 81 | 82 | The codes are sorted by computing a count of codes for each length, 83 | creating from that a table of starting indices for each length in the 84 | sorted table, and then entering the symbols in order in the sorted 85 | table. The sorted table is work[], with that space being provided by 86 | the caller. 87 | 88 | The length counts are used for other purposes as well, i.e. finding 89 | the minimum and maximum length codes, determining if there are any 90 | codes at all, checking for a valid set of lengths, and looking ahead 91 | at length counts to determine sub-table sizes when building the 92 | decoding tables. 93 | */ 94 | 95 | /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ 96 | for (len = 0; len <= MAXBITS; len++) { 97 | count[len] = 0; 98 | } 99 | for (sym = 0; sym < codes; sym++) { 100 | count[lens[lens_index + sym]]++; 101 | } 102 | 103 | /* bound code lengths, force root to be within code lengths */ 104 | root = bits; 105 | for (max = MAXBITS; max >= 1; max--) { 106 | if (count[max] !== 0) { break; } 107 | } 108 | if (root > max) { 109 | root = max; 110 | } 111 | if (max === 0) { /* no symbols to code at all */ 112 | //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ 113 | //table.bits[opts.table_index] = 1; //here.bits = (var char)1; 114 | //table.val[opts.table_index++] = 0; //here.val = (var short)0; 115 | table[table_index++] = (1 << 24) | (64 << 16) | 0; 116 | 117 | 118 | //table.op[opts.table_index] = 64; 119 | //table.bits[opts.table_index] = 1; 120 | //table.val[opts.table_index++] = 0; 121 | table[table_index++] = (1 << 24) | (64 << 16) | 0; 122 | 123 | opts.bits = 1; 124 | return 0; /* no symbols, but wait for decoding to report error */ 125 | } 126 | for (min = 1; min < max; min++) { 127 | if (count[min] !== 0) { break; } 128 | } 129 | if (root < min) { 130 | root = min; 131 | } 132 | 133 | /* check for an over-subscribed or incomplete set of lengths */ 134 | left = 1; 135 | for (len = 1; len <= MAXBITS; len++) { 136 | left <<= 1; 137 | left -= count[len]; 138 | if (left < 0) { 139 | return -1; 140 | } /* over-subscribed */ 141 | } 142 | if (left > 0 && (type === CODES || max !== 1)) { 143 | return -1; /* incomplete set */ 144 | } 145 | 146 | /* generate offsets into symbol table for each length for sorting */ 147 | offs[1] = 0; 148 | for (len = 1; len < MAXBITS; len++) { 149 | offs[len + 1] = offs[len] + count[len]; 150 | } 151 | 152 | /* sort symbols by length, by symbol order within each length */ 153 | for (sym = 0; sym < codes; sym++) { 154 | if (lens[lens_index + sym] !== 0) { 155 | work[offs[lens[lens_index + sym]]++] = sym; 156 | } 157 | } 158 | 159 | /* 160 | Create and fill in decoding tables. In this loop, the table being 161 | filled is at next and has curr index bits. The code being used is huff 162 | with length len. That code is converted to an index by dropping drop 163 | bits off of the bottom. For codes where len is less than drop + curr, 164 | those top drop + curr - len bits are incremented through all values to 165 | fill the table with replicated entries. 166 | 167 | root is the number of index bits for the root table. When len exceeds 168 | root, sub-tables are created pointed to by the root entry with an index 169 | of the low root bits of huff. This is saved in low to check for when a 170 | new sub-table should be started. drop is zero when the root table is 171 | being filled, and drop is root when sub-tables are being filled. 172 | 173 | When a new sub-table is needed, it is necessary to look ahead in the 174 | code lengths to determine what size sub-table is needed. The length 175 | counts are used for this, and so count[] is decremented as codes are 176 | entered in the tables. 177 | 178 | used keeps track of how many table entries have been allocated from the 179 | provided *table space. It is checked for LENS and DIST tables against 180 | the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in 181 | the initial root table size constants. See the comments in inftrees.h 182 | for more information. 183 | 184 | sym increments through all symbols, and the loop terminates when 185 | all codes of length max, i.e. all codes, have been processed. This 186 | routine permits incomplete codes, so another loop after this one fills 187 | in the rest of the decoding tables with invalid code markers. 188 | */ 189 | 190 | /* set up for code type */ 191 | // poor man optimization - use if-else instead of switch, 192 | // to avoid deopts in old v8 193 | if (type === CODES) { 194 | base = extra = work; /* dummy value--not used */ 195 | end = 19; 196 | 197 | } else if (type === LENS) { 198 | base = lbase; 199 | base_index -= 257; 200 | extra = lext; 201 | extra_index -= 257; 202 | end = 256; 203 | 204 | } else { /* DISTS */ 205 | base = dbase; 206 | extra = dext; 207 | end = -1; 208 | } 209 | 210 | /* initialize opts for loop */ 211 | huff = 0; /* starting code */ 212 | sym = 0; /* starting code symbol */ 213 | len = min; /* starting code length */ 214 | next = table_index; /* current table to fill in */ 215 | curr = root; /* current table index bits */ 216 | drop = 0; /* current bits to drop from code for index */ 217 | low = -1; /* trigger new sub-table when len > root */ 218 | used = 1 << root; /* use root table entries */ 219 | mask = used - 1; /* mask for comparing low */ 220 | 221 | /* check available table space */ 222 | if ((type === LENS && used > ENOUGH_LENS) || 223 | (type === DISTS && used > ENOUGH_DISTS)) { 224 | return 1; 225 | } 226 | 227 | /* process all codes and make table entries */ 228 | for (;;) { 229 | /* create table entry */ 230 | here_bits = len - drop; 231 | if (work[sym] < end) { 232 | here_op = 0; 233 | here_val = work[sym]; 234 | } 235 | else if (work[sym] > end) { 236 | here_op = extra[extra_index + work[sym]]; 237 | here_val = base[base_index + work[sym]]; 238 | } 239 | else { 240 | here_op = 32 + 64; /* end of block */ 241 | here_val = 0; 242 | } 243 | 244 | /* replicate for those indices with low len bits equal to huff */ 245 | incr = 1 << (len - drop); 246 | fill = 1 << curr; 247 | min = fill; /* save offset to next table */ 248 | do { 249 | fill -= incr; 250 | table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; 251 | } while (fill !== 0); 252 | 253 | /* backwards increment the len-bit code huff */ 254 | incr = 1 << (len - 1); 255 | while (huff & incr) { 256 | incr >>= 1; 257 | } 258 | if (incr !== 0) { 259 | huff &= incr - 1; 260 | huff += incr; 261 | } else { 262 | huff = 0; 263 | } 264 | 265 | /* go to next symbol, update count, len */ 266 | sym++; 267 | if (--count[len] === 0) { 268 | if (len === max) { break; } 269 | len = lens[lens_index + work[sym]]; 270 | } 271 | 272 | /* create new sub-table if needed */ 273 | if (len > root && (huff & mask) !== low) { 274 | /* if first time, transition to sub-tables */ 275 | if (drop === 0) { 276 | drop = root; 277 | } 278 | 279 | /* increment past last table */ 280 | next += min; /* here min is 1 << curr */ 281 | 282 | /* determine length of next table */ 283 | curr = len - drop; 284 | left = 1 << curr; 285 | while (curr + drop < max) { 286 | left -= count[curr + drop]; 287 | if (left <= 0) { break; } 288 | curr++; 289 | left <<= 1; 290 | } 291 | 292 | /* check for enough space */ 293 | used += 1 << curr; 294 | if ((type === LENS && used > ENOUGH_LENS) || 295 | (type === DISTS && used > ENOUGH_DISTS)) { 296 | return 1; 297 | } 298 | 299 | /* point entry in root table to sub-table */ 300 | low = huff & mask; 301 | /*table.op[low] = curr; 302 | table.bits[low] = root; 303 | table.val[low] = next - opts.table_index;*/ 304 | table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; 305 | } 306 | } 307 | 308 | /* fill in remaining table entry if code is incomplete (guaranteed to have 309 | at most one remaining entry, since if the code is incomplete, the 310 | maximum code length that was allowed to get this far is one bit) */ 311 | if (huff !== 0) { 312 | //table.op[next + huff] = 64; /* invalid code marker */ 313 | //table.bits[next + huff] = len - drop; 314 | //table.val[next + huff] = 0; 315 | table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; 316 | } 317 | 318 | /* set return parameters */ 319 | //opts.table_index += used; 320 | opts.bits = root; 321 | return 0; 322 | }; 323 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/lib/zlib/messages.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 2: 'need dictionary', /* Z_NEED_DICT 2 */ 3 | 1: 'stream end', /* Z_STREAM_END 1 */ 4 | 0: '', /* Z_OK 0 */ 5 | '-1': 'file error', /* Z_ERRNO (-1) */ 6 | '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ 7 | '-3': 'data error', /* Z_DATA_ERROR (-3) */ 8 | '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ 9 | '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ 10 | '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ 11 | }; 12 | -------------------------------------------------------------------------------- /assets/novnc/vendor/pako/lib/zlib/zstream.js: -------------------------------------------------------------------------------- 1 | export default function ZStream() { 2 | /* next input byte */ 3 | this.input = null; // JS specific, because we have no pointers 4 | this.next_in = 0; 5 | /* number of bytes available at input */ 6 | this.avail_in = 0; 7 | /* total number of input bytes read so far */ 8 | this.total_in = 0; 9 | /* next output byte should be put there */ 10 | this.output = null; // JS specific, because we have no pointers 11 | this.next_out = 0; 12 | /* remaining free space at output */ 13 | this.avail_out = 0; 14 | /* total number of bytes output so far */ 15 | this.total_out = 0; 16 | /* last error message, NULL if no error */ 17 | this.msg = ''/*Z_NULL*/; 18 | /* not visible by applications */ 19 | this.state = null; 20 | /* best guess about the data type: binary or text */ 21 | this.data_type = 2/*Z_UNKNOWN*/; 22 | /* adler32 value of the uncompressed data */ 23 | this.adler = 0; 24 | } 25 | -------------------------------------------------------------------------------- /bin/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vprix/agent/8007eba1545f2adb7c2eb2209f75b5af3b40d74e/bin/.keep -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## VPrix-Agent 2 | 3 | `Agent` 是整个虚拟桌面项目的核心程序。 4 | 5 | 它作为Docker容器内启动的第一个程序,有着类似于linux系统的init的作用。 6 | 7 | 它的功能可以分为以下几类。 8 | 9 | ##### 基础功能 10 | 11 | 1. 根据环境变量,启动`xfce`桌面和`xvnc` 12 | 2. novnc的静态文件服务器 13 | 3. 登录权限校验 14 | 4. 虚拟桌面分享 15 | 5. vnc的代理服务器。 16 | 6. 虚拟桌面的上传下载接口 17 | 7. 虚拟桌面的声音输入输出接口 18 | 19 | ##### 扩展功能 20 | 21 | 1. 执行远程命令 22 | 2. 统计运行数据上报。 23 | 24 | 注意,`agen`t需要搭配`vprix-image`项目中的容器镜像使用,这些镜像再编译的时候已经把`agent`编译进了`image`中。 -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vprix/agent/8007eba1545f2adb7c2eb2209f75b5af3b40d74e/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vprix-agent - 虚拟桌面agent使用文档 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 |
20 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/startx: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This is just a sample implementation of a slightly less primitive 4 | # interface than xinit. It looks for user .xinitrc and .xserverrc 5 | # files, then system xinitrc and xserverrc files, else lets xinit choose 6 | # its default. The system xinitrc should probably do things like check 7 | # for .Xresources files and merge them in, start up a window manager, 8 | # and pop a clock and several xterms. 9 | # 10 | # Site administrators are STRONGLY urged to write nicer versions. 11 | # 12 | 13 | unset XDG_SESSION_COOKIE 14 | unset DBUS_SESSION_BUS_ADDRESS 15 | unset SESSION_MANAGER 16 | userclientrc=$HOME/.xinitrc 17 | sysclientrc=/etc/X11/xinit/xinitrc 18 | 19 | userserverrc=$HOME/.xserverrc 20 | sysserverrc=/etc/X11/xinit/xserverrc 21 | defaultclient=xterm 22 | defaultserver=/usr/bin/X 23 | defaultclientargs="" 24 | defaultserverargs="" 25 | defaultdisplay=":0" 26 | clientargs="" 27 | serverargs="" 28 | vtarg="" 29 | enable_xauth=1 30 | 31 | 32 | # Automatically determine an unused $DISPLAY 33 | d=0 34 | while true ; do 35 | [ -e "/tmp/.X$d-lock" -o -S "/tmp/.X11-unix/X$d" ] || 36 | 37 | grep -q "/tmp/.X11-unix/X$d" "/proc/net/unix" || 38 | 39 | break 40 | d=$(($d + 1)) 41 | done 42 | defaultdisplay=":$d" 43 | unset d 44 | 45 | whoseargs="client" 46 | while [ x"$1" != x ]; do 47 | case "$1" in 48 | # '' required to prevent cpp from treating "/*" as a C comment. 49 | /''*|\./''*) 50 | if [ "$whoseargs" = "client" ]; then 51 | if [ x"$client" = x ] && [ x"$clientargs" = x ]; then 52 | client="$1" 53 | else 54 | clientargs="$clientargs $1" 55 | fi 56 | else 57 | if [ x"$server" = x ] && [ x"$serverargs" = x ]; then 58 | server="$1" 59 | else 60 | serverargs="$serverargs $1" 61 | fi 62 | fi 63 | ;; 64 | --) 65 | whoseargs="server" 66 | ;; 67 | *) 68 | if [ "$whoseargs" = "client" ]; then 69 | clientargs="$clientargs $1" 70 | else 71 | # display must be the FIRST server argument 72 | if [ x"$serverargs" = x ] && \ 73 | expr "$1" : ':[0-9][0-9]*$' > /dev/null 2>&1; then 74 | display="$1" 75 | else 76 | serverargs="$serverargs $1" 77 | fi 78 | fi 79 | ;; 80 | esac 81 | shift 82 | done 83 | 84 | # process client arguments 85 | if [ x"$client" = x ]; then 86 | client=$defaultclient 87 | 88 | # For compatibility reasons, only use startxrc if there were no client command line arguments 89 | if [ x"$clientargs" = x ]; then 90 | if [ -f "$userclientrc" ]; then 91 | client=$userclientrc 92 | elif [ -f "$sysclientrc" ]; then 93 | client=$sysclientrc 94 | fi 95 | fi 96 | fi 97 | 98 | # if no client arguments, use defaults 99 | if [ x"$clientargs" = x ]; then 100 | clientargs=$defaultclientargs 101 | fi 102 | 103 | # process server arguments 104 | if [ x"$server" = x ]; then 105 | server=$defaultserver 106 | 107 | 108 | # When starting the defaultserver start X on the current tty to avoid 109 | # the startx session being seen as inactive: 110 | # "https://bugzilla.redhat.com/show_bug.cgi?id=806491" 111 | tty=$(tty) 112 | if expr match "$tty" '^/dev/tty[0-9]\+$' > /dev/null; then 113 | tty_num=$(echo "$tty" | grep -oE '[0-9]+$') 114 | vtarg="vt$tty_num -keeptty" 115 | fi 116 | 117 | 118 | # For compatibility reasons, only use xserverrc if there were no server command line arguments 119 | if [ x"$serverargs" = x -a x"$display" = x ]; then 120 | if [ -f "$userserverrc" ]; then 121 | server=$userserverrc 122 | elif [ -f "$sysserverrc" ]; then 123 | server=$sysserverrc 124 | fi 125 | fi 126 | fi 127 | 128 | # if no server arguments, use defaults 129 | if [ x"$serverargs" = x ]; then 130 | serverargs=$defaultserverargs 131 | fi 132 | 133 | # if no vt is specified add vtarg (which may be empty) 134 | have_vtarg="no" 135 | for i in $serverargs; do 136 | if expr match "$i" '^vt[0-9]\+$' > /dev/null; then 137 | have_vtarg="yes" 138 | fi 139 | done 140 | if [ "$have_vtarg" = "no" ]; then 141 | serverargs="$serverargs $vtarg" 142 | fi 143 | 144 | # if no display, use default 145 | if [ x"$display" = x ]; then 146 | display=$defaultdisplay 147 | fi 148 | 149 | if [ x"$enable_xauth" = x1 ] ; then 150 | if [ x"$XAUTHORITY" = x ]; then 151 | XAUTHORITY=$HOME/.Xauthority 152 | export XAUTHORITY 153 | fi 154 | 155 | removelist= 156 | 157 | # set up default Xauth info for this machine 158 | case `uname` in 159 | Linux*) 160 | if [ -z "`hostname --version 2>&1 | grep GNU`" ]; then 161 | hostname=`hostname -f` 162 | else 163 | hostname=`hostname` 164 | fi 165 | ;; 166 | *) 167 | hostname=`hostname` 168 | ;; 169 | esac 170 | 171 | authdisplay=${display:-:0} 172 | 173 | mcookie=`/usr/bin/mcookie` 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | if test x"$mcookie" = x; then 182 | echo "Couldn't create cookie" 183 | exit 1 184 | fi 185 | dummy=0 186 | 187 | # create a file with auth information for the server. ':0' is a dummy. 188 | xserverauthfile=$HOME/.serverauth.$$ 189 | trap "rm -f '$xserverauthfile'" HUP INT QUIT ILL TRAP KILL BUS TERM 190 | xauth -q -f "$xserverauthfile" << EOF 191 | add :$dummy . $mcookie 192 | EOF 193 | 194 | 195 | 196 | 197 | serverargs=${serverargs}" -auth "${xserverauthfile} 198 | 199 | 200 | # now add the same credentials to the client authority file 201 | # if '$displayname' already exists do not overwrite it as another 202 | # server man need it. Add them to the '$xserverauthfile' instead. 203 | for displayname in $authdisplay $hostname$authdisplay; do 204 | authcookie=`xauth list "$displayname" \ 205 | | sed -n "s/.*$displayname[[:space:]*].*[[:space:]*]//p"` 2>/dev/null; 206 | if [ "z${authcookie}" = "z" ] ; then 207 | xauth -q << EOF 208 | add $displayname . $mcookie 209 | EOF 210 | removelist="$displayname $removelist" 211 | else 212 | dummy=$(($dummy+1)); 213 | xauth -q -f "$xserverauthfile" << EOF 214 | add :$dummy . $authcookie 215 | EOF 216 | fi 217 | done 218 | fi 219 | 220 | 221 | 222 | 223 | xinit "$client" $clientargs -- "$server" $display $serverargs 224 | 225 | retval=$? 226 | 227 | if [ x"$enable_xauth" = x1 ] ; then 228 | if [ x"$removelist" != x ]; then 229 | xauth remove $removelist 230 | fi 231 | if [ x"$xserverauthfile" != x ]; then 232 | rm -f "$xserverauthfile" 233 | fi 234 | fi 235 | 236 | 237 | 238 | 239 | 240 | if command -v deallocvt > /dev/null 2>&1; then 241 | deallocvt 242 | fi 243 | exit $retval 244 | -------------------------------------------------------------------------------- /docs/xsession: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # /etc/X11/Xsession 4 | # 5 | # global Xsession file -- used by display managers and xinit (startx) 6 | 7 | # $Id: Xsession 967 2005-12-27 07:20:55Z dnusinow $ 8 | 9 | set -e 10 | 11 | PROGNAME=Xsession 12 | 13 | message () { 14 | # pretty-print messages of arbitrary length; use xmessage if it 15 | # is available and $DISPLAY is set 16 | MESSAGE="$PROGNAME: $*" 17 | echo "$MESSAGE" | fold -s -w ${COLUMNS:-80} >&2 18 | if [ -n "$DISPLAY" ] && which xmessage > /dev/null 2>&1; then 19 | echo "$MESSAGE" | fold -s -w ${COLUMNS:-80} | xmessage -center -file - 20 | fi 21 | } 22 | 23 | message_nonl () { 24 | # pretty-print messages of arbitrary length (no trailing newline); use 25 | # xmessage if it is available and $DISPLAY is set 26 | MESSAGE="$PROGNAME: $*" 27 | echo -n "$MESSAGE" | fold -s -w ${COLUMNS:-80} >&2; 28 | if [ -n "$DISPLAY" ] && which xmessage > /dev/null 2>&1; then 29 | echo -n "$MESSAGE" | fold -s -w ${COLUMNS:-80} | xmessage -center -file - 30 | fi 31 | } 32 | 33 | errormsg () { 34 | # exit script with error 35 | message "$*" 36 | exit 1 37 | } 38 | 39 | internal_errormsg () { 40 | # exit script with error; essentially a "THIS SHOULD NEVER HAPPEN" message 41 | # One big call to message() for the sake of xmessage; if we had two then 42 | # the user would have dismissed the error we want reported before seeing the 43 | # request to report it. 44 | errormsg "$*" \ 45 | "Please report the installed version of the \"x11-common\"" \ 46 | "package and the complete text of this error message to" \ 47 | "." 48 | } 49 | 50 | # initialize variables for use by all session scripts 51 | 52 | OPTIONFILE=/etc/X11/Xsession.options 53 | 54 | SYSRESOURCES=/etc/X11/Xresources 55 | USRRESOURCES=$HOME/.Xresources 56 | 57 | SYSSESSIONDIR=/etc/X11/Xsession.d 58 | USERXSESSION=$HOME/.xsession 59 | USERXSESSIONRC=$HOME/.xsessionrc 60 | ALTUSERXSESSION=$HOME/.Xsession 61 | ERRFILE=$HOME/.xsession-errors 62 | 63 | # attempt to create an error file; abort if we cannot 64 | if (umask 077 && touch "$ERRFILE") 2> /dev/null && [ -w "$ERRFILE" ] && 65 | [ ! -L "$ERRFILE" ]; then 66 | chmod 600 "$ERRFILE" 67 | elif ERRFILE=$(tempfile 2> /dev/null); then 68 | if ! ln -sf "$ERRFILE" "${TMPDIR:=/tmp}/xsession-$USER"; then 69 | message "warning: unable to symlink \"$TMPDIR/xsession-$USER\" to" \ 70 | "\"$ERRFILE\"; look for session log/errors in" \ 71 | "\"$TMPDIR/xsession-$USER\"." 72 | fi 73 | else 74 | errormsg "unable to create X session log/error file; aborting." 75 | fi 76 | 77 | # truncate ERRFILE if it is too big to avoid disk usage DoS 78 | if [ "`stat -c%s \"$ERRFILE\"`" -gt 500000 ]; then 79 | T=`mktemp -p "$HOME"` 80 | tail -c 500000 "$ERRFILE" > "$T" && mv -f "$T" "$ERRFILE" || rm -f "$T" 81 | fi 82 | 83 | exec >>"$ERRFILE" 2>&1 84 | 85 | echo "$PROGNAME: X session started for $LOGNAME at $(date)" 86 | 87 | # sanity check; is our session script directory present? 88 | if [ ! -d "$SYSSESSIONDIR" ]; then 89 | errormsg "no \"$SYSSESSIONDIR\" directory found; aborting." 90 | fi 91 | 92 | # Attempt to create a file of non-zero length in /tmp; a full filesystem can 93 | # cause mysterious X session failures. We do not use touch, :, or test -w 94 | # because they won't actually create a file with contents. We also let standard 95 | # error from tempfile and echo go to the error file to aid the user in 96 | # determining what went wrong. 97 | WRITE_TEST=$(tempfile) 98 | if ! echo "*" >>"$WRITE_TEST"; then 99 | message "warning: unable to write to ${WRITE_TEST%/*}; X session may exit" \ 100 | "with an error" 101 | fi 102 | rm -f "$WRITE_TEST" 103 | 104 | # use run-parts to source every file in the session directory; we source 105 | # instead of executing so that the variables and functions defined above 106 | # are available to the scripts, and so that they can pass variables to each 107 | # other 108 | SESSIONFILES=$(run-parts --list $SYSSESSIONDIR) 109 | if [ -n "$SESSIONFILES" ]; then 110 | set +e 111 | for SESSIONFILE in $SESSIONFILES; do 112 | . $SESSIONFILE 113 | done 114 | set -e 115 | fi 116 | 117 | exit 0 118 | 119 | # vim:set ai et sts=2 sw=2 tw=80: -------------------------------------------------------------------------------- /env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import "github.com/gogf/gf/os/genv" 4 | 5 | const ( 6 | VprixAgentLoginUsername = "VPRIX_AGENT_LOGIN_USERNAME" 7 | VprixAgentLoginPassword = "VPRIX_AGENT_LOGIN_PASSWORD" 8 | ) 9 | 10 | // UserName 获取网页端登录的用户名 11 | func UserName() string { 12 | return genv.Get(VprixAgentLoginUsername, "vprix") 13 | } 14 | 15 | // Password 获取网页端登录的密码 16 | func Password() string { 17 | return genv.Get(VprixAgentLoginPassword, "vprix") 18 | } 19 | 20 | // User 获取登录桌面的用户名 21 | func User() string { 22 | return genv.Get("VPRIX_USER", "vprix-user") 23 | } 24 | 25 | // Home 获取家目录 26 | func Home() string { 27 | return genv.Get("HOME", "/home/vprix-user") 28 | } 29 | 30 | // Display 显示端口 31 | func Display() string { 32 | return genv.Get("DISPLAY", ":0") 33 | } 34 | 35 | // VprixPort 获取服务监听端口 36 | func VprixPort() int { 37 | return genv.GetVar("VPRIX_PORT", 8080).Int() 38 | } 39 | 40 | // VprixAgentPath 获取agent的安装路径 41 | func VprixAgentPath() string { 42 | return genv.Get("VPRIX_AGENT_PATH", "/usr/share/vprix_agent") 43 | } 44 | 45 | // VncColDepth 获取vnc 界面的色深 46 | func VncColDepth() int { 47 | return genv.GetVar("VNC_COL_DEPTH", 24).Int() 48 | } 49 | 50 | // VncResolution 获取vnc的默认分辨率 51 | func VncResolution() string { 52 | return genv.Get("VNC_RESOLUTION", "1280x1024") 53 | } 54 | 55 | // VncPassword 获取设置的vnc的密码,注意,最多只有8位起效 56 | func VncPassword() string { 57 | return genv.Get("VNC_PASSWORD", "vprix.com") 58 | } 59 | 60 | // VncOptions 获取vnc的设置 61 | func VncOptions() string { 62 | return genv.Get("VNC_OPTIONS", "") 63 | } 64 | 65 | func IsUbuntu() bool { 66 | return genv.GetVar("DISTRO", "ubuntu").String() == "ubuntu" 67 | } 68 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module agent 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gobuffalo/packr/v2 v2.8.3 7 | github.com/gogf/gf v1.16.9 8 | github.com/osgochina/dmicro v0.6.2 9 | github.com/vprix/vncproxy v1.1.0 10 | golang.org/x/net v0.0.0-20220517181318-183a9ca12b87 11 | ) 12 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "agent/app" 5 | "github.com/gogf/gf/os/gproc" 6 | "github.com/osgochina/dmicro/easyservice" 7 | "github.com/osgochina/dmicro/logger" 8 | "github.com/osgochina/dmicro/supervisor/process" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if gproc.IsChild() { 14 | logger.Infof("子进程%d启动成功", os.Getpid()) 15 | easyservice.Setup(func(svr *easyservice.EasyService) { 16 | //注册服务停止时要执行法方法 17 | svr.BeforeStop(func(service *easyservice.EasyService) bool { 18 | logger.Info("BeforeStop: agent server stop") 19 | return true 20 | }) 21 | svr.AddSandBox(app.NewSandBoxServer(svr)) 22 | }) 23 | } else { 24 | // 回收僵尸进程 25 | process.ReapZombie() 26 | m := gproc.NewManager() 27 | p := m.NewProcess(os.Args[0], os.Args, os.Environ()) 28 | _, err := p.Start() 29 | if err != nil { 30 | logger.Warningf("启动子进程报错:%v", err) 31 | return 32 | } 33 | logger.Infof("父进程%d启动成功", os.Getpid()) 34 | _ = p.Wait() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/customexec/command.go: -------------------------------------------------------------------------------- 1 | package customexec 2 | 3 | import ( 4 | "agent/env" 5 | "github.com/osgochina/dmicro/logger" 6 | "os/exec" 7 | "os/user" 8 | "strconv" 9 | "strings" 10 | "syscall" 11 | ) 12 | 13 | type Cmd struct { 14 | *exec.Cmd 15 | } 16 | 17 | func Command(name string, arg ...string) *Cmd { 18 | cmd := exec.Command(name, arg...) 19 | c := &Cmd{} 20 | cmd.SysProcAttr = &syscall.SysProcAttr{} 21 | c.Cmd = cmd 22 | return c 23 | } 24 | 25 | func (that *Cmd) SetUser(username ...string) { 26 | userName := env.User() 27 | if len(username) > 0 && len(username[0]) > 0 { 28 | userName = username[0] 29 | } 30 | //判断是否传入了用户组 31 | pos := strings.Index(userName, ":") 32 | groupName := "" 33 | if pos != -1 { 34 | groupName = userName[pos+1:] 35 | userName = userName[0:pos] 36 | } 37 | u, err := user.Lookup(userName) 38 | if err != nil { 39 | logger.Error(err) 40 | return 41 | } 42 | uid, err := strconv.ParseUint(u.Uid, 10, 32) 43 | if err != nil { 44 | logger.Error(err) 45 | return 46 | } 47 | gid, err := strconv.ParseUint(u.Gid, 10, 32) 48 | if err != nil && groupName == "" { 49 | logger.Error(err) 50 | return 51 | } 52 | if groupName != "" { 53 | g, err := user.LookupGroup(groupName) 54 | if err != nil { 55 | logger.Error(err) 56 | return 57 | } 58 | gid, err = strconv.ParseUint(g.Gid, 10, 32) 59 | if err != nil { 60 | logger.Error(err) 61 | return 62 | } 63 | } 64 | that.Cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid), NoSetGroups: true} 65 | } 66 | 67 | func (that *Cmd) Start() error { 68 | return that.Cmd.Start() 69 | } 70 | -------------------------------------------------------------------------------- /pkg/desktop/vncopts.go: -------------------------------------------------------------------------------- 1 | package desktop 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gogf/gf/container/gmap" 6 | "github.com/gogf/gf/errors/gerror" 7 | "github.com/gogf/gf/text/gregex" 8 | "github.com/gogf/gf/text/gstr" 9 | "github.com/gogf/gf/util/gconv" 10 | ) 11 | 12 | // XVncOpts xvnc 服务启动的参数 https://tigervnc.org/doc/Xvnc.html 13 | type XVncOpts struct { 14 | // 每个桌面都有一个名字,客户端会显示改名字,默认是"x11" 15 | Desktop string 16 | // 桌面分辨率 格式是width x height,指定要创建的桌面的大小。默认值为 1024x768。 17 | Geometry string 18 | // 指定要创建的桌面位的像素深度。默认值为 24,其他可能值为 16 和 32。任何其他内容都可能导致应用程序出现奇怪的行为,并可能阻止服务器启动。 19 | Depth int 20 | // 指定用于服务器的像素格式(BGRnnn 或 RGBnnn)。深度 16 的默认值为 RGB565,深度 24 和 32 的默认值为 RGB888。 21 | PixelFormat string 22 | 23 | //监听的端口 格式是 interface IP address,所以这里的赋值是 "127.0.0.1" 24 | Interface string 25 | // 这极大地改变了Xvnc的行为,使它可以从inetd启动。请参阅下面关于inetd用法的部分。 26 | //inetd bool 27 | // 指定xVnc接收客户端链接的tcp端口,默认是5900+displayNumber 28 | RfbPort int 29 | //是否使用 ipv4 30 | UseIPv4 bool 31 | //是否使用ipv6 32 | UseIPv6 bool 33 | // 指定xVnc接收客户端链接地址,可以不适用端口 34 | RfbUnixPath string 35 | //unix socket 文件权限默认是 0600 36 | RfbUnixMode string 37 | 38 | //Time in milliseconds to wait for a viewer which is blocking the server. 39 | //This is necessary because the server is single-threaded and sometimes blocks 40 | //until the viewer has finished sending or receiving a message 41 | //- note that this does not mean an update will be aborted after this time. Default is 20000 (20 seconds). 42 | // −ClientWaitTimeMillis 这连个参数作用是一样 43 | RfbWait int 44 | // 服务器的密码地址,−PasswordFile passwd-file 这两个也是一样的 45 | RfbAuth string 46 | 47 | //是否支持剪切板,默认是支持 48 | AcceptCutText bool 49 | // 剪切板支持的最大长度,默认262144 50 | MaxCutText int 51 | // 是否发送剪切内容到客户端,默认是支持 52 | SendCutText bool 53 | 54 | //Send the primary selection and cut buffer to the server as well as the clipboard selection. Default is on. 55 | SendPrimary bool 56 | 57 | //接收客户端的鼠标事件 默认打开 58 | AcceptPointerEvents bool 59 | 60 | // 接收客户端的键盘事件 默认打开 61 | AcceptKeyEvents bool 62 | 63 | // 接收客户端的桌面尺寸设置 默认打开 64 | AcceptSetDesktopSize bool 65 | 66 | //如果传入的连接是non-shared,则断开现有客户端。默认的是。 67 | //如果DisconnectClients为false,则当有一个活跃的客户端时,将拒绝一个新的非共享连接。 68 | //当与NeverShared组合时,这意味着一次只允许一个客户端。 69 | DisconnectClients bool 70 | 71 | // 不共享桌面,默认是off 72 | NeverShared bool 73 | 74 | // 永远共享桌面 默认是off 75 | AlwaysShared bool 76 | 77 | // 使用 Protocol3.3的协议,默认是off 78 | Protocol33 bool 79 | 80 | // 每秒发送给客户端的帧数,当桌面更新速度很快的时候,会压缩发送,注意,这只控制最大速率,当资源有限时,客户端可能会获得较低的速率。默认是60。 81 | FrameRate int 82 | 83 | // 对framebuffer进行像素比较,减少不必要的更新。可以是0(关闭),1(总是)或2(自动)。默认是2。 84 | CompareFB int 85 | //ZRLE编码的Zlib压缩级别(不影响Tight编码)。可接受的值在0到9之间。默认情况下,使用zlib(3)压缩库提供的标准压缩级别。 86 | ZlibLevel int 87 | // 使用改进的压缩算法进行Hextile编码,通过略微增加CPU时间的代价获得更好的压缩比。默认的on。 88 | ImprovedHextile bool 89 | 90 | //指定为传入连接使用哪个安全方案。有效值为None、VncAuth、Plain、TLSNone、TLSVnc、TLSPlain、X509None、X509Vnc和X509Plain,用逗号分隔。 91 | //默认是VncAuth, TLSVnc。 92 | SecurityTypes string 93 | 94 | //客户端访问服务器时必须提供的密码的模糊二进制编码。使用该参数是不安全的,请使用PasswordFile参数。 95 | Password string 96 | 97 | // 一个以逗号分隔的用户名列表,允许通过任何“普通”安全类型(Plain, TLSPlain等)进行身份验证。指定*以允许任何用户使用此安全类型进行身份验证。默认是拒绝所有用户。 98 | PlainUsers string 99 | 100 | //对使用任何“Plain”安全类型的用户进行身份验证时使用的PAM服务名称。默认是vnc。 101 | PAMService string 102 | 103 | // 用于所有基于X509的安全类型(X509None, X509Vnc等)的PEM格式的X509证书的路径。 104 | X509Cert string 105 | 106 | //X509Cert中给出的证书的私钥对应部分。也必须是PEM格式。 107 | X509Key string 108 | 109 | //GnuTLS优先级字符串,控制TLS session握手算法。有关可能的值,请参阅GnuTLS手册。默认是正常的。 110 | GnuTLSPriority string 111 | 112 | // 如果主机的身份验证多次失败,则暂时拒绝它的连接。默认on。 113 | UseBlacklist bool 114 | 115 | // 在任何一台主机被列入黑名单之前,允许从该主机进行未经身份验证的连接尝试的次数。默认是5。 116 | BlacklistThreshold int 117 | 118 | // 首次加入黑名单时应用的初始超时时间。主机不能重新尝试连接,直到超时过期。默认是10。 119 | BlacklistTimeout int 120 | 121 | // 闲置VNC连接将被丢弃的秒数。默认值是0,这意味着空闲的连接永远不会被删除。 122 | IdleTimeout int 123 | 124 | // 当N秒内没有客户端连接时终止。默认值为0。 125 | MaxDisconnectionTime int 126 | 127 | // 当客户端已连接N秒时终止。默认值为0。 128 | MaxConnectionTime int 129 | 130 | // 在用户不活动N秒后终止。默认值为0。 131 | MaxIdleTime int 132 | 133 | // 提示桌面用户显式地接受或拒绝传入连接。默认是off。 134 | // 为了支持QueryConnect, vncconfig(1)程序必须在桌面上运行。 135 | QueryConnect bool 136 | 137 | //拒绝连接之前显示接受连接对话框的秒数。默认是10。 138 | QueryConnectTimeout int 139 | 140 | // 只允许来自同一机器的连接。如果您使用SSH并且想要停止来自任何其他主机的非SSH连接,则此功能非常有用。 141 | Localhost bool 142 | 143 | // 配置调试日志设置。 144 | //Dest目前可以是stderr、stdout或syslog, 145 | //level介于0和100之间,100表示最详细的输出。 146 | //logname通常是*表示全部,但如果您知道其“LogWriter”的名称,则可以将目标指向特定的源文件。 147 | //默认是*:stderr: 30。 148 | // logname:dest:level 149 | Log string 150 | 151 | // 更改键盘字符的映射 152 | // Mapping是一个逗号分隔的字符映射字符串,每个字符的形式为char->char或char<>char,其中char是一个十六进制键符号。例如,要交换"和@符号,你需要指定以下参数: 153 | // RemapKeys=0x22<>0x40 154 | RemapKeys string 155 | 156 | // 受NumLock影响的键通常需要插入一个假的Shift,以便生成正确的符号。 157 | // 打开这个选项可以避免这些额外的假Shift事件,但可能会导致一个稍微不同的符号(例如,一个回车而不是键盘上的回车)。 158 | AvoidShiftNumLock bool 159 | 160 | // 直接发送键盘事件,避免将它们映射到当前的键盘布局。这有效地使键盘根据服务器上配置的布局而不是客户端上配置的布局表现。默认是off。 161 | RawKeyboard bool 162 | 163 | // 逗号分隔的参数列表,可以使用VNC扩展修改。例如,可以在正在运行的会话中使用vncconfig(1)程序修改参数。 164 | // 如果Xvnc在不同的用户下运行,而不是在允许覆盖参数的程序下运行,那么允许覆盖参数(如PAMService或PasswordFile)会对安全性产生负面影响。 165 | // 当设置NoClipboard参数时,允许覆盖SendCutText和AcceptCutText不起作用。 166 | //默认的是desktop、AcceptPointerEvents SendCutText AcceptCutText, SendPrimary SetPrimary。 167 | AllowOverride string 168 | } 169 | 170 | func NewXVncOpts() *XVncOpts { 171 | opts := &XVncOpts{} 172 | opts.Desktop = "x11" 173 | opts.Geometry = "1024x768" 174 | opts.Depth = 24 175 | opts.PixelFormat = "RGB888" 176 | opts.RfbWait = 30000 177 | opts.UseIPv4 = true 178 | opts.UseIPv6 = false 179 | opts.AcceptCutText = false 180 | opts.SendCutText = false 181 | opts.SendPrimary = true 182 | opts.AcceptPointerEvents = true 183 | opts.AcceptKeyEvents = true 184 | opts.AcceptSetDesktopSize = true 185 | opts.DisconnectClients = true 186 | opts.NeverShared = false 187 | opts.Protocol33 = false 188 | opts.FrameRate = 60 189 | opts.CompareFB = 2 190 | opts.ZlibLevel = 3 191 | opts.ImprovedHextile = true 192 | opts.SecurityTypes = "VncAuth,TLSVnc" 193 | opts.UseBlacklist = true 194 | opts.BlacklistThreshold = 5 195 | opts.BlacklistTimeout = 10 196 | opts.IdleTimeout = 0 197 | opts.MaxDisconnectionTime = 0 198 | opts.MaxConnectionTime = 0 199 | opts.MaxIdleTime = 0 200 | opts.QueryConnect = false 201 | opts.QueryConnectTimeout = 10 202 | opts.Localhost = true 203 | return opts 204 | } 205 | 206 | // 判断分辨率和色深是否符合规则 207 | func (that *XVncOpts) checkGeometryAndDepth() error { 208 | result, err := gregex.Match(`^(\d+)x(\d+)$`, gconv.Bytes(that.Geometry)) 209 | if err != nil { 210 | return err 211 | } 212 | if len(result) != 3 { 213 | return gerror.Newf("%s 规则不正确", that.Geometry) 214 | } 215 | width := gconv.Int(result[1]) 216 | height := gconv.Int(result[2]) 217 | if width < 1 || height < 1 { 218 | return gerror.Newf("geometry %s is invalid", that.Geometry) 219 | } 220 | that.Geometry = fmt.Sprintf("%dx%d", width, height) 221 | if that.Depth < 8 || that.Depth > 32 { 222 | return gerror.New("Depth must be between 8 and 32") 223 | } 224 | return nil 225 | } 226 | 227 | func (that *XVncOpts) Check() error { 228 | err := that.checkGeometryAndDepth() 229 | if err != nil { 230 | return err 231 | } 232 | return nil 233 | } 234 | 235 | func (that *XVncOpts) Array() []string { 236 | var args = gmap.NewStrStrMap() 237 | args.Set("geometry", that.Geometry) 238 | args.Set("depth", gconv.String(that.Depth)) 239 | args.Set("pixelformat", that.PixelFormat) 240 | args.Set("desktop", that.Desktop) 241 | 242 | if len(that.Interface) > 0 { 243 | args.Set("interface", that.Interface) 244 | } 245 | if that.RfbPort > 0 { 246 | args.Set("rfbport", gconv.String(that.RfbPort)) 247 | } 248 | if !that.UseIPv4 { 249 | args.Set("UseIPv4", "") 250 | } 251 | 252 | if !that.UseIPv6 { 253 | args.Set("UseIPv6", "") 254 | } 255 | 256 | if len(that.RfbUnixPath) > 0 { 257 | args.Set("rfbunixpath", that.RfbUnixPath) 258 | } 259 | 260 | if len(that.RfbUnixMode) > 0 { 261 | args.Set("rfbunixmode", that.RfbUnixMode) 262 | } 263 | 264 | if that.RfbWait > 0 { 265 | args.Set("rfbwait", gconv.String(that.RfbWait)) 266 | } 267 | 268 | if len(that.RfbAuth) > 0 { 269 | args.Set("rfbauth", that.RfbAuth) 270 | } 271 | if !that.AcceptCutText { 272 | args.Set("AcceptCutText", "") 273 | } 274 | 275 | if that.MaxCutText > 0 { 276 | args.Set("MaxCutText", gconv.String(that.MaxCutText)) 277 | } 278 | if !that.SendCutText { 279 | args.Set("SendCutText", "") 280 | } 281 | 282 | if !that.SendPrimary { 283 | args.Set("SendPrimary", "") 284 | } 285 | 286 | if !that.AcceptPointerEvents { 287 | args.Set("AcceptPointerEvents", "") 288 | } 289 | 290 | if !that.AcceptKeyEvents { 291 | args.Set("AcceptKeyEvents", "") 292 | } 293 | 294 | if !that.AcceptSetDesktopSize { 295 | args.Set("AcceptSetDesktopSize", "") 296 | } 297 | 298 | if !that.DisconnectClients { 299 | args.Set("DisconnectClients", "") 300 | } 301 | 302 | if that.NeverShared { 303 | args.Set("NeverShared", "") 304 | } 305 | 306 | if that.AlwaysShared { 307 | args.Set("AlwaysShared", "") 308 | } 309 | if that.Protocol33 { 310 | args.Set("Protocol3.3", "") 311 | } 312 | 313 | if that.FrameRate != 60 { 314 | args.Set("FrameRate", gconv.String(that.FrameRate)) 315 | } 316 | 317 | if that.CompareFB != 2 { 318 | args.Set("CompareFB", gconv.String(that.CompareFB)) 319 | } 320 | if that.ZlibLevel != 3 { 321 | args.Set("ZlibLevel", gconv.String(that.ZlibLevel)) 322 | } 323 | if !that.ImprovedHextile { 324 | args.Set("ImprovedHextile", "") 325 | } 326 | if len(that.SecurityTypes) > 0 { 327 | args.Set("SecurityTypes", that.SecurityTypes) 328 | } 329 | if len(that.PlainUsers) > 0 { 330 | args.Set("PlainUsers", that.PlainUsers) 331 | } 332 | if len(that.PAMService) > 0 { 333 | args.Set("PAMService", that.PAMService) 334 | } 335 | if len(that.X509Cert) > 0 { 336 | args.Set("X509Cert", that.X509Cert) 337 | } 338 | if len(that.X509Key) > 0 { 339 | args.Set("X509Key", that.X509Key) 340 | } 341 | if len(that.GnuTLSPriority) > 0 { 342 | args.Set("GnuTLSPriority", that.GnuTLSPriority) 343 | } 344 | if !that.UseBlacklist { 345 | args.Set("UseBlacklist", "") 346 | } 347 | 348 | if that.BlacklistThreshold != 5 { 349 | args.Set("BlacklistThreshold", gconv.String(that.BlacklistThreshold)) 350 | } 351 | if that.BlacklistTimeout != 10 { 352 | args.Set("BlacklistTimeout", gconv.String(that.BlacklistTimeout)) 353 | } 354 | if that.IdleTimeout != 0 { 355 | args.Set("IdleTimeout", gconv.String(that.IdleTimeout)) 356 | } 357 | if that.MaxDisconnectionTime != 0 { 358 | args.Set("MaxDisconnectionTime", gconv.String(that.MaxDisconnectionTime)) 359 | } 360 | if that.MaxConnectionTime != 0 { 361 | args.Set("MaxConnectionTime", gconv.String(that.MaxConnectionTime)) 362 | } 363 | if that.MaxIdleTime != 0 { 364 | args.Set("MaxIdleTime", gconv.String(that.MaxIdleTime)) 365 | } 366 | if that.QueryConnect { 367 | args.Set("QueryConnect", "") 368 | } 369 | if that.QueryConnectTimeout != 10 { 370 | args.Set("QueryConnectTimeout", gconv.String(that.QueryConnectTimeout)) 371 | } 372 | if that.Localhost { 373 | args.Set("localhost", "") 374 | } 375 | if len(that.Log) > 0 { 376 | args.Set("Log", that.Log) 377 | } 378 | if len(that.RemapKeys) > 0 { 379 | args.Set("RemapKeys", that.RemapKeys) 380 | } 381 | if that.AvoidShiftNumLock { 382 | args.Set("AvoidShiftNumLock", "") 383 | } 384 | if that.RawKeyboard { 385 | args.Set("RawKeyboard", "") 386 | } 387 | if len(that.AllowOverride) > 0 { 388 | args.Set("AllowOverride", that.AllowOverride) 389 | } 390 | // 取消xWindow窗口的监听 391 | args.Set("nolisten", "tcp") 392 | var params []string 393 | for k, v := range args.Map() { 394 | params = append(params, "-"+k) 395 | if len(v) > 0 { 396 | params = append(params, v) 397 | } 398 | } 399 | return params 400 | } 401 | func (that *XVncOpts) String() string { 402 | params := that.Array() 403 | return gstr.Implode(" ", params) 404 | } 405 | -------------------------------------------------------------------------------- /pkg/desktop/xInit.go: -------------------------------------------------------------------------------- 1 | package desktop 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gogf/gf/os/genv" 6 | "github.com/gogf/gf/text/gstr" 7 | "github.com/gogf/gf/util/gconv" 8 | "github.com/osgochina/dmicro/logger" 9 | "github.com/osgochina/dmicro/supervisor/process" 10 | ) 11 | 12 | type XInit struct { 13 | opts *XVncOpts 14 | displayNumber int 15 | chroot string 16 | user string 17 | logPath string 18 | } 19 | 20 | func NewXInit(opts *XVncOpts) *XInit { 21 | return &XInit{ 22 | opts: opts, 23 | } 24 | } 25 | func (that *XInit) Opts() *XVncOpts { 26 | return that.opts 27 | } 28 | 29 | func (that *XInit) SetOpts(opts *XVncOpts) { 30 | that.opts = opts 31 | } 32 | 33 | func (that *XInit) DisplayNumber() int { 34 | return that.displayNumber 35 | } 36 | 37 | func (that *XInit) SetDisplayNumber(displayNumber int) { 38 | that.displayNumber = displayNumber 39 | } 40 | 41 | func (that *XInit) Chroot() string { 42 | return that.chroot 43 | } 44 | 45 | func (that *XInit) SetChroot(chroot string) { 46 | that.chroot = chroot 47 | } 48 | 49 | func (that *XInit) User() string { 50 | return that.user 51 | } 52 | 53 | func (that *XInit) SetUser(user string) { 54 | that.user = user 55 | } 56 | 57 | func (that *XInit) LogPath() string { 58 | return that.logPath 59 | } 60 | 61 | func (that *XInit) SetLogPath(logPath string) { 62 | that.logPath = logPath 63 | } 64 | 65 | func (that *XInit) initEnv() { 66 | _ = genv.Remove("SESSION_MANAGER") 67 | _ = genv.Remove("DBUS_SESSION_BUS_ADDRESS") 68 | _ = genv.Set("XDG_SESSION_TYPE", "x11") 69 | _ = genv.Set("XKL_XMODMAP_DISABLE", gconv.String(1)) 70 | _ = genv.Set("DISPLAY", fmt.Sprintf(":%d", that.displayNumber)) 71 | } 72 | 73 | func (that *XInit) NewProcess() (*process.ProcEntry, error) { 74 | that.initEnv() 75 | proc := process.NewProcEntry("/usr/bin/xinit") 76 | // /usr/bin/xinit /usr/bin/dbus-launch startxfce4 -- /usr/bin/Xvnc :0 ...... 77 | proc.SetArgs(append( 78 | []string{ 79 | "/usr/bin/startxfce4", 80 | "--", 81 | "/usr/bin/Xvnc", 82 | fmt.Sprintf(":%d", that.displayNumber)}, that.opts.Array()...), 83 | ) 84 | proc.SetDirectory(that.chroot) 85 | proc.SetUser(that.user) 86 | proc.SetAutoReStart("false") 87 | proc.SetRedirectStderr(true) 88 | proc.SetStdoutLogfile(that.logPath) 89 | proc.SetStdoutLogFileMaxBytes("100MB") 90 | proc.SetStderrLogFileBackups(10) 91 | proc.SetEnvironment(genv.All()) 92 | logger.Info("启动xinit命令: ", "/usr/bin/xinit ", gstr.Implode(" ", proc.Args())) 93 | logger.Info("启动环境变量:", gstr.Implode(";", genv.All())) 94 | return proc, nil 95 | } 96 | -------------------------------------------------------------------------------- /pkg/desktop/xfce.go: -------------------------------------------------------------------------------- 1 | package desktop 2 | 3 | // Xfce 启动xfce桌面 4 | type Xfce struct { 5 | // 显示器编号 6 | DisplayNumber int 7 | // 计算机名 8 | Hostname string 9 | // 桌面名称 10 | DesktopName string 11 | 12 | //显示地址 13 | Display string 14 | 15 | // home目录 16 | Home string 17 | 18 | // 进程运行用户 19 | User string 20 | 21 | // 运行日志文件路径 22 | LogPath string 23 | } 24 | 25 | // 26 | //func NewXfce() *Xfce { 27 | // return &Xfce{} 28 | //} 29 | // 30 | //// XStartup 启动Xface桌面 31 | //func (that *Xfce) XStartup() (*process.ProcEntry, error) { 32 | // xStartup := NewXStartup() 33 | // if gfile.Exists(fmt.Sprintf(":%d", that.DisplayNumber)) || 34 | // gfile.Exists(fmt.Sprintf("/usr/spool/sockets/X11/%d", that.DisplayNumber)) { 35 | // that.Display = fmt.Sprintf(":%d", that.DisplayNumber) 36 | // } else { 37 | // that.Display = fmt.Sprintf("%s:%d", that.Hostname, that.DisplayNumber) 38 | // } 39 | // 40 | // xStartup.SetDisplay(that.Display) 41 | // xStartup.SetVncDesktop(that.DesktopName) 42 | // xStartup.InitEnv() 43 | // //xSet := NewXSet() 44 | // //err := xSet.DpmsClose() 45 | // //if err != nil { 46 | // // return nil, gerror.Newf("Dpms Close:%v", err) 47 | // //} 48 | // //err = xSet.NoBlank() 49 | // //if err != nil { 50 | // // return nil, gerror.Newf("NoBlank:%v", err) 51 | // //} 52 | // //err = xSet.SOff() 53 | // //if err != nil { 54 | // // return nil, gerror.Newf("SOff:%v", err) 55 | // //} 56 | // proc := process.NewProcEntry("/usr/bin/dbus-launch", []string{"startxfce4"}) 57 | // proc.SetDirectory(that.Home) 58 | // //proc.SetUser(that.User) 59 | // proc.SetUser(env.User()) 60 | // // 初始化工具要使用的环境变量,后期需要放到一个统一的地方 61 | // proc.SetEnvironment(genv.All()) 62 | // proc.SetAutoReStart("false") 63 | // proc.SetRedirectStderr(true) 64 | // proc.SetStdoutLogfile(that.LogPath) 65 | // proc.SetStdoutLogFileMaxBytes("100MB") 66 | // proc.SetStderrLogFileBackups(10) 67 | // logger.Info("启动xfce命令: /usr/bin/dbus-launch startxfce4") 68 | // logger.Info("启动环境变量:", gstr.Implode(";", genv.All())) 69 | // 70 | // return proc, nil 71 | //} 72 | // 73 | //// XStartupUbuntu 启动Xface桌面 74 | //func (that *Xfce) XStartupUbuntu() (*process.ProcEntry, error) { 75 | // xStartup := NewXStartup() 76 | // if gfile.Exists(fmt.Sprintf(":%d", that.DisplayNumber)) || 77 | // gfile.Exists(fmt.Sprintf("/usr/spool/sockets/X11/%d", that.DisplayNumber)) { 78 | // that.Display = fmt.Sprintf(":%d", that.DisplayNumber) 79 | // } else { 80 | // that.Display = fmt.Sprintf("%s:%d", that.Hostname, that.DisplayNumber) 81 | // } 82 | // 83 | // xStartup.SetDisplay(that.Display) 84 | // xStartup.SetVncDesktop(that.DesktopName) 85 | // xStartup.InitEnv() 86 | // proc := process.NewProcEntry("/etc/X11/Xsession", []string{"startxfce4"}) 87 | // proc.SetDirectory(that.Home) 88 | // //proc.SetUser(that.User) 89 | // //proc.SetUser(env.User()) 90 | // // 初始化工具要使用的环境变量,后期需要放到一个统一的地方 91 | // proc.SetEnvironment(genv.All()) 92 | // proc.SetAutoReStart("false") 93 | // proc.SetRedirectStderr(true) 94 | // proc.SetStdoutLogfile(that.LogPath) 95 | // proc.SetStdoutLogFileMaxBytes("100MB") 96 | // proc.SetStderrLogFileBackups(10) 97 | // logger.Info("启动xfce命令: /etc/X11/Xsession startxfce4") 98 | // logger.Info("启动环境变量:", gstr.Implode(";", genv.All())) 99 | // 100 | // return proc, nil 101 | //} 102 | -------------------------------------------------------------------------------- /pkg/desktop/xset.go: -------------------------------------------------------------------------------- 1 | package desktop 2 | 3 | import ( 4 | "agent/pkg/customexec" 5 | "github.com/gogf/gf/os/genv" 6 | ) 7 | 8 | // 命令说明 http://linux.51yip.com/search/xset 9 | 10 | type XSet struct{} 11 | 12 | func NewXSet() *XSet { 13 | return &XSet{} 14 | } 15 | 16 | // B 打开和关闭电脑的嘟嘟的提示音,比如我们打开文件的是否,出错的时候发出的声音。但是听音乐还是可以照常听的 17 | func (that *XSet) B(open bool) error { 18 | var b = "off" 19 | if open { 20 | b = "on" 21 | } 22 | cmd := customexec.Command("/usr/bin/xset", "b", b) 23 | cmd.Env = genv.All() 24 | cmd.SetUser() 25 | err := cmd.Run() 26 | if err != nil { 27 | return err 28 | } 29 | return nil 30 | } 31 | 32 | // BC -bc 关闭调试版本兼容机制 33 | // bc 打开调试版本兼容机制 34 | func (that *XSet) BC(open bool) error { 35 | var b = "-bc" 36 | if open { 37 | b = "bc" 38 | } 39 | cmd := customexec.Command("/usr/bin/xset", b) 40 | cmd.Env = genv.All() 41 | cmd.SetUser() 42 | err := cmd.Run() 43 | if err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | //C c 控制键盘的按键声 50 | // 关闭/打开 51 | func (that *XSet) C(open bool) error { 52 | var c = "off" 53 | if open { 54 | c = "on" 55 | } 56 | cmd := customexec.Command("/usr/bin/xset", "c", c) 57 | cmd.Env = genv.All() 58 | cmd.SetUser() 59 | err := cmd.Run() 60 | if err != nil { 61 | return err 62 | } 63 | return nil 64 | } 65 | 66 | // 命令 描述 67 | //xset s off 禁用屏保清空 68 | //xset s 3600 3600 将清空时间设置到 1 小时 69 | //xset -dpms 关闭 DPMS 70 | //xset s off -dpms 禁用 DPMS 并阻止屏幕清空 71 | //xset dpms force off 立即关闭屏幕 72 | //xset dpms force standby 待机界面 73 | //xset dpms force suspend 休眠界面 74 | 75 | // DpmsOpen 打开电源之星,主要用来省电的 76 | func (that *XSet) DpmsOpen() error { 77 | cmd := customexec.Command("/usr/bin/xset", "+dpms") 78 | cmd.Env = genv.All() 79 | cmd.SetUser() 80 | err := cmd.Run() 81 | if err != nil { 82 | return err 83 | } 84 | return nil 85 | } 86 | 87 | // DpmsClose 关闭电源之星,不用省电 88 | func (that *XSet) DpmsClose() error { 89 | cmd := customexec.Command("/usr/bin/xset", "-dpms") 90 | cmd.Env = genv.All() 91 | cmd.SetUser() 92 | err := cmd.Run() 93 | if err != nil { 94 | return err 95 | } 96 | return nil 97 | } 98 | 99 | // FP 字体搜索 100 | // 增加一个字体搜寻目录。 101 | // 删除一个字体搜寻目录。 102 | // 重新设置字体搜寻目录。 103 | // $ xset +fp /usr/local/fonts/Type1 104 | // $ xset fp+ /usr/local/fonts/bitmap 105 | func (that *XSet) FP(dir string, opt int) error { 106 | fp := "fp=" 107 | if opt == 0 { 108 | fp = "fp=" 109 | } else if opt > 0 { 110 | fp = "fp+" 111 | } else if opt < 0 { 112 | fp = "fp-" 113 | } 114 | cmd := customexec.Command("/usr/bin/xset", fp, dir) 115 | cmd.Env = genv.All() 116 | cmd.SetUser() 117 | err := cmd.Run() 118 | if err != nil { 119 | return err 120 | } 121 | return nil 122 | } 123 | 124 | // NoBlank 屏保后画面是一个图案) 125 | func (that *XSet) NoBlank() error { 126 | cmd := customexec.Command("/usr/bin/xset", "s", "noblank") 127 | cmd.Env = genv.All() 128 | cmd.SetUser() 129 | err := cmd.Run() 130 | if err != nil { 131 | return err 132 | } 133 | return nil 134 | } 135 | 136 | // Blank 屏保后画面为黑色的 137 | func (that *XSet) Blank() error { 138 | cmd := customexec.Command("/usr/bin/xset", "s", "blank") 139 | cmd.Env = genv.All() 140 | cmd.SetUser() 141 | err := cmd.Run() 142 | if err != nil { 143 | return err 144 | } 145 | return nil 146 | } 147 | 148 | // SOff 禁用屏保清空 149 | func (that *XSet) SOff() error { 150 | cmd := customexec.Command("/usr/bin/xset", "s", "off") 151 | cmd.Env = genv.All() 152 | cmd.SetUser() 153 | err := cmd.Run() 154 | if err != nil { 155 | return err 156 | } 157 | return nil 158 | } 159 | -------------------------------------------------------------------------------- /pkg/desktop/xstartup.go: -------------------------------------------------------------------------------- 1 | package desktop 2 | 3 | import ( 4 | "agent/pkg/customexec" 5 | "github.com/gogf/gf/os/genv" 6 | "github.com/gogf/gf/util/gconv" 7 | ) 8 | 9 | type XStartup struct { 10 | display string 11 | vncDesktop string 12 | } 13 | 14 | func NewXStartup() *XStartup { 15 | return &XStartup{} 16 | } 17 | 18 | func (that *XStartup) Display() string { 19 | return that.display 20 | } 21 | 22 | func (that *XStartup) SetDisplay(display string) { 23 | that.display = display 24 | } 25 | 26 | func (that *XStartup) VncDesktop() string { 27 | return that.vncDesktop 28 | } 29 | 30 | func (that *XStartup) SetVncDesktop(vncDesktop string) { 31 | that.vncDesktop = vncDesktop 32 | } 33 | 34 | func (that *XStartup) InitEnv() { 35 | _ = genv.Remove("SESSION_MANAGER") 36 | _ = genv.Remove("DBUS_SESSION_BUS_ADDRESS") 37 | _ = genv.Set("XDG_SESSION_TYPE", "x11") 38 | _ = genv.Set("XKL_XMODMAP_DISABLE", gconv.String(1)) 39 | _ = genv.Set("DISPLAY", that.display) 40 | _ = genv.Set("VNCDESKTOP", that.vncDesktop) 41 | } 42 | 43 | func (that *XStartup) VncConfig() error { 44 | cmd := customexec.Command("/usr/bin/vncconfig", "-nowin") 45 | cmd.Env = genv.All() 46 | cmd.SetUser() 47 | err := cmd.Start() 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | 54 | func (that *XStartup) DbusLaunch(session string) error { 55 | cmd := customexec.Command("/usr/bin/dbus-launch", session) 56 | cmd.Env = genv.All() 57 | cmd.SetUser() 58 | err := cmd.Start() 59 | if err != nil { 60 | return err 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/desktop/xvnc.go: -------------------------------------------------------------------------------- 1 | package desktop 2 | 3 | //type XVnc struct { 4 | // opts *XVncOpts 5 | // DisplayNumber int 6 | // Dir string 7 | // User string 8 | // LogPath string 9 | //} 10 | // 11 | //func NewXVnc(opts *XVncOpts) *XVnc { 12 | // return &XVnc{ 13 | // opts: opts, 14 | // } 15 | //} 16 | // 17 | //// NewXVncProcess 创建xvnc的进程 18 | //func (that *XVnc) NewXVncProcess() (*process.ProcEntry, error) { 19 | // proc := process.NewProcEntry("xinit") 20 | // proc.SetArgs(append([]string{"/usr/bin/dbus-launch", "startxfce4", "--", "/usr/bin/Xvnc", fmt.Sprintf(":%d", that.DisplayNumber)}, that.opts.Array()...)) 21 | // //proc.SetArgs(append([]string{"/etc/X11/Xsession", "startxfce4", "--", "/usr/bin/Xvnc", fmt.Sprintf(":%d", that.DisplayNumber)}, that.opts.Array()...)) 22 | // proc.SetDirectory(that.Dir) 23 | // proc.SetUser(env.User()) 24 | // proc.SetAutoReStart("false") 25 | // proc.SetRedirectStderr(true) 26 | // proc.SetStdoutLogfile(that.LogPath) 27 | // proc.SetStdoutLogFileMaxBytes("100MB") 28 | // proc.SetStderrLogFileBackups(10) 29 | // proc.SetEnvironment(genv.All()) 30 | // logger.Info("启动xvnc命令: ", "xinit ", gstr.Implode(" ", proc.Args())) 31 | // logger.Info("启动环境变量:", gstr.Implode(";", genv.All())) 32 | // return proc, nil 33 | //} 34 | -------------------------------------------------------------------------------- /pkg/vncpasswd/vncpasswd.go: -------------------------------------------------------------------------------- 1 | package vncpasswd 2 | 3 | import "crypto/des" 4 | 5 | func AuthVNCEncode(plaintext []byte) []byte { 6 | // 定义vnc加密的key 7 | key := []byte{23, 82, 107, 6, 35, 78, 88, 7} 8 | 9 | // 密码的每个字节都需要反转。这是一个 10 | // VNC客户端和服务器的非rfc记录行为 11 | for i := range key { 12 | key[i] = (key[i]&0x55)<<1 | (key[i]&0xAA)>>1 // Swap adjacent bits 13 | key[i] = (key[i]&0x33)<<2 | (key[i]&0xCC)>>2 // Swap adjacent pairs 14 | key[i] = (key[i]&0x0F)<<4 | (key[i]&0xF0)>>4 // Swap the 2 halves 15 | } 16 | 17 | // Encrypt challenge with key. 18 | cipher, err := des.NewCipher(key) 19 | if err != nil { 20 | return nil 21 | } 22 | for i := 0; i < len(plaintext); i += cipher.BlockSize() { 23 | cipher.Encrypt(plaintext[i:i+cipher.BlockSize()], plaintext[i:i+cipher.BlockSize()]) 24 | } 25 | 26 | return plaintext 27 | } 28 | 29 | func AuthVNCDecrypt(cipherB []byte) []byte { 30 | // 定义vnc加密的key 31 | key := []byte{23, 82, 107, 6, 35, 78, 88, 7} 32 | 33 | // 密码的每个字节都需要反转。这是一个 34 | // VNC客户端和服务器的非rfc记录行为 35 | for i := range key { 36 | key[i] = (key[i]&0x55)<<1 | (key[i]&0xAA)>>1 // Swap adjacent bits 37 | key[i] = (key[i]&0x33)<<2 | (key[i]&0xCC)>>2 // Swap adjacent pairs 38 | key[i] = (key[i]&0x0F)<<4 | (key[i]&0xF0)>>4 // Swap the 2 halves 39 | } 40 | 41 | // Encrypt challenge with key. 42 | cipher, err := des.NewCipher(key) 43 | if err != nil { 44 | return nil 45 | } 46 | var plaintext []byte 47 | for i := 0; i < len(cipherB); i += cipher.BlockSize() { 48 | tmp := make([]byte, i+cipher.BlockSize()) 49 | cipher.Decrypt(tmp, cipherB[i:i+cipher.BlockSize()]) 50 | plaintext = append(plaintext, tmp...) 51 | } 52 | return plaintext 53 | } 54 | -------------------------------------------------------------------------------- /pkg/vncpasswd/vncpasswd_test.go: -------------------------------------------------------------------------------- 1 | package vncpasswd 2 | 3 | import ( 4 | "github.com/gogf/gf/test/gtest" 5 | "github.com/gogf/gf/util/gconv" 6 | "testing" 7 | ) 8 | 9 | func TestVncByGo(t *testing.T) { 10 | gtest.C(t, func(t *gtest.T) { 11 | var plaintext = "lzmlzm11" 12 | b := AuthVNCEncode(gconv.Bytes(plaintext)) 13 | t.Log(string(AuthVNCDecrypt(b))) 14 | t.Assert(AuthVNCDecrypt(b), plaintext) 15 | }) 16 | } 17 | --------------------------------------------------------------------------------