├── tutorials
├── bin
│ └── .gitkeep
├── advanced_single_binary_distribution
│ ├── provisioner
│ │ ├── .gitignore
│ │ └── single_binary_thrust_provisioner.go
│ ├── .gitignore
│ └── advanced_single_binary_distribution.go
├── provisioner
│ └── tutorial_provisioner.go
├── basic_multiple_windows
│ └── basic_multiple_windows.go
├── advanced_window_devtools
│ └── advanced_window_devtools.go
├── basic_window
│ └── basic_window.go
├── basic_frameless_window
│ └── basic_frameless_window.go
├── basic_session
│ └── basic_session.go
├── basic_webserver_app
│ └── basic_webserver_app.go
├── basic_menu
│ └── basic_menu.go
├── basic_remote_messaging
│ └── basic_remote_messaging.go
├── basic_menu_events
│ └── basic_menu_events.go
├── advanced_session
│ └── advanced_session.go
└── basic_window_events
│ └── basic_window_events.go
├── examples
├── chat
│ ├── .gitignore
│ ├── client
│ │ ├── run_client
│ │ └── client.go
│ ├── server
│ │ ├── run_server
│ │ ├── chat.html
│ │ ├── chat.css
│ │ ├── chat.html.go
│ │ ├── chat.js
│ │ ├── server.go
│ │ ├── chat.css.go
│ │ └── chat.js.go
│ ├── README.md
│ └── LICENSE
└── jankybrowser
│ ├── .gitignore
│ ├── run
│ ├── package.json
│ ├── _README.md
│ ├── browser.go
│ ├── index.js
│ ├── browser.html
│ └── browser.html.go
├── Gomfile
├── .travis.yml
├── lib
├── common
│ └── common.go
├── spawn
│ ├── provisioner.go
│ ├── unzip.go
│ ├── download.go
│ ├── helper_linux.go
│ ├── helper_windows.go
│ ├── spawn_process.go
│ └── helper_darwin.go
├── bindings
│ ├── menu
│ │ ├── menu_sync.go
│ │ ├── menuitem.go
│ │ └── menu.go
│ ├── session
│ │ ├── session_cookie.go
│ │ ├── session_invokable.go
│ │ ├── dummy_session.go
│ │ └── session.go
│ └── window
│ │ └── window.go
├── events
│ └── eventhandler.go
├── dispatcher
│ └── dispatcher.go
├── connection
│ └── connection.go
└── commands
│ └── commands.go
├── CHANGELOG.md
├── main.go
├── LICENSE
├── .gitignore
├── thrust
├── thrust_test.go
└── thrust.go
├── TODO.md
└── README.md
/tutorials/bin/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/chat/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/jankybrowser/.gitignore:
--------------------------------------------------------------------------------
1 | jankybrowser
--------------------------------------------------------------------------------
/Gomfile:
--------------------------------------------------------------------------------
1 | gom 'github.com/miketheprogrammer/go-thrust'
2 |
--------------------------------------------------------------------------------
/tutorials/advanced_single_binary_distribution/provisioner/.gitignore:
--------------------------------------------------------------------------------
1 | vendor.go
2 |
--------------------------------------------------------------------------------
/tutorials/advanced_single_binary_distribution/.gitignore:
--------------------------------------------------------------------------------
1 | advanced_single_binary_distribution
2 |
--------------------------------------------------------------------------------
/examples/chat/client/run_client:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 |
5 | exec go run client.go "$@"
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - tip
4 | before_install:
5 | - go get github.com/mattn/gom
6 | script:
7 | - $HOME/gopath/bin/gom install
8 | - $HOME/gopath/bin/gom test ./_vendor/src/github.com/miketheprogrammer/go-thrust/
9 | - $HOME/gopath/bin/gom test ./_vendor/src/github.com/miketheprogrammer/go-thrust/lib/...
10 |
--------------------------------------------------------------------------------
/examples/chat/server/run_server:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | cd -- "$(dirname -- "$0")"
5 |
6 | declare -a ASSETS=(chat.html chat.js chat.css)
7 |
8 | for asset in "${ASSETS[@]}"; do
9 | var="$(printf "$asset" | tr -c 'a-zA-Z' '_')"
10 | bin2go -pkg=main -in="$asset" -out="$asset.go" "$var"
11 | done
12 | exec go run server.go "${ASSETS[@]/%/.go}" "$@"
13 |
--------------------------------------------------------------------------------
/examples/jankybrowser/run:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | cd -- "$(dirname -- "$0")"
5 |
6 | declare -a ASSETS=(browser.html)
7 |
8 | for asset in "${ASSETS[@]}"; do
9 | var="$(printf "$asset" | tr -c 'a-zA-Z' '_')"
10 | bin2go -pkg=main -in="$asset" -out="$asset.go" "$var"
11 | done
12 | echo go run browser.go "${ASSETS[@]/%/.go}" "$@"
13 | exec go run browser.go "${ASSETS[@]/%/.go}" "$@"
14 |
--------------------------------------------------------------------------------
/examples/jankybrowser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jankybrowser",
3 | "version": "1.2.0",
4 | "description": "",
5 | "bin": {
6 | "jankybrowser": "jankybrowser"
7 | },
8 | "main": "index.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "async": "^0.9.0",
16 | "minimist": "^1.1.0",
17 | "node-thrust": "^0.7.5"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tutorials/provisioner/tutorial_provisioner.go:
--------------------------------------------------------------------------------
1 | package tutorial
2 |
3 | import "github.com/miketheprogrammer/go-thrust/lib/spawn"
4 |
5 | /*
6 | Default implementation of Provisioner
7 | */
8 | type TutorialProvisioner struct{}
9 |
10 | func NewTutorialProvisioner() TutorialProvisioner {
11 | return TutorialProvisioner{}
12 | }
13 |
14 | func (tp TutorialProvisioner) Provision() error {
15 | spawn.SetBaseDirectory("") // Means use the users home directory
16 | return spawn.Bootstrap()
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/examples/jankybrowser/_README.md:
--------------------------------------------------------------------------------
1 | JankyBrowser
2 | ============
3 |
4 | The only cross-platform browser that fits in a Gist!
5 |
6 | One line install. Works on Linux, MacOSX and Windows.
7 |
8 | #### Local Install
9 | ```
10 | $> npm install http://gist.github.com/morganrallen/f07f59802884bcdcad4a/download
11 | $> node node_modules/jankybrowser
12 | ```
13 |
14 | #### Global Install
15 | ```
16 | $> npm install -g http://gist.github.com/morganrallen/f07f59802884bcdcad4a/download
17 | $> jankybrowser
18 | ```
19 |
20 | JankyBrowser is based on [Thrust](https://github.com/breach/thrust)
21 |
--------------------------------------------------------------------------------
/lib/common/common.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "os"
7 | "strings"
8 | )
9 |
10 | //Global ID tracking for Commands
11 | //Could probably move this to a factory function
12 | var ActionId uint = 0
13 | var LogLevel string = "enabled"
14 | var Log *log.Logger = log.New(ioutil.Discard, "Go-Thrust:", 3)
15 |
16 | func InitLogger(level string) {
17 | LogLevel = strings.ToLower(level)
18 | switch LogLevel {
19 | case "none":
20 | Log = log.New(ioutil.Discard, "Go-Thrust:", 3)
21 | default:
22 | Log = log.New(os.Stdout, "Go-Thrust:", 3)
23 | }
24 | Log.Print("Thrust Client:: Initializing")
25 | }
26 |
--------------------------------------------------------------------------------
/lib/spawn/provisioner.go:
--------------------------------------------------------------------------------
1 | package spawn
2 |
3 | type Provisioner interface {
4 | Provision() error
5 | }
6 |
7 | // Package global provisioner, use SetProvisioner to set this variable
8 | var provisioner Provisioner
9 |
10 | // Sets the current provision to use during spawn.Run()
11 | func SetProvisioner(p Provisioner) {
12 | provisioner = p
13 | }
14 |
15 | /*
16 | Default implementation of Provisioner
17 | */
18 | type ThrustProvisioner struct{}
19 |
20 | func NewThrustProvisioner() ThrustProvisioner {
21 | return ThrustProvisioner{}
22 | }
23 |
24 | func (tp ThrustProvisioner) Provision() error {
25 | return Bootstrap()
26 | }
27 |
--------------------------------------------------------------------------------
/examples/chat/client/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 |
7 | "github.com/miketheprogrammer/go-thrust/thrust"
8 | )
9 |
10 | var (
11 | host = flag.String("host", "0.0.0.0", "IP address to bind to")
12 | port = flag.Int("port", 8000, "TCP port to listen on")
13 | )
14 |
15 | func main() {
16 | flag.Parse()
17 | thrust.InitLogger()
18 | thrust.Start()
19 |
20 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
21 | RootUrl: fmt.Sprintf("http://127.0.0.1:%d", *port),
22 | })
23 | thrustWindow.Show()
24 | thrustWindow.Focus()
25 | // BLOCKING - Dont run before youve excuted all commands you want first.
26 | thrust.LockThread()
27 | }
28 |
--------------------------------------------------------------------------------
/examples/chat/server/chat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Welcome to the Go-Thrust Chat Demo.
11 |
12 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/bindings/menu/menu_sync.go:
--------------------------------------------------------------------------------
1 | package menu
2 |
3 | import "github.com/miketheprogrammer/go-thrust/lib/commands"
4 |
5 | type ChildCommand struct {
6 | Command *commands.Command
7 | Child *Menu
8 | }
9 |
10 | type MenuSync struct {
11 | /* Channels for singaling queues */
12 | ReadyChan chan bool
13 | DisplayedChan chan bool
14 | ChildStableChan chan uint
15 | TreeStableChan chan bool
16 |
17 | /*Queues for preserving command order and Priority*/
18 | ReadyQueue []*commands.Command
19 | DisplayedQueue []*commands.Command
20 | // Not Exactly a Queue, more of a priority queue. Send out the first child that is stable.
21 | ChildStableQueue []*ChildCommand
22 | TreeStableQueue []*commands.Command
23 |
24 | /* Channels for control */
25 | QuitChan chan bool
26 | }
27 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | NOV132014 (Version 0.3.3):
2 | Improved CPU Usage - More battery power for all.
3 | Added basic chat client/server example.
4 |
5 | NOV132014 (Version 0.3.2):
6 |
7 | Important Changes:
8 | - Upgraded Thrust Core to version 0.7.5
9 | - Implemented Session and SessionInvokable interface.
10 | - Windows Support.
11 | - Added optional auto download to spawn.Run()
12 | - Remove return values from spawn.Run()
13 |
14 | Trivial Changes:
15 | - Added several tutorials
16 |
17 | NOV102014 (Version 0.2.4):
18 |
19 | Important Changes:
20 | - Upgraded Thrust Core to version 0.7.4
21 | - Removed Menu.AttachToWindow
22 | - Added Menu.Popup
23 | - Support added for Webview.
24 |
25 | Trivial Changes:
26 | - demo.go now opens public/index.html
27 | - public/index.html now has an example of webview
28 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/codegangsta/cli"
8 | "github.com/miketheprogrammer/go-thrust/lib/spawn"
9 | )
10 |
11 | func install(c *cli.Context) {
12 | spawn.SetBaseDirectory("") // Default to usr.homedir.
13 | tp := spawn.NewThrustProvisioner()
14 | if err := tp.Provision(); err != nil {
15 | panic(err)
16 | }
17 | fmt.Println("Thrust installed")
18 | }
19 |
20 | func main() {
21 | app := cli.NewApp()
22 | app.Name = "go-thrust"
23 | app.Usage = "Tools to developp Thrust applications in Go"
24 |
25 | app.Commands = []cli.Command{
26 | {
27 | Name: "install",
28 | ShortName: "i",
29 | Usage: "Install Thrust",
30 | Action: func(c *cli.Context) {
31 | install(c)
32 | },
33 | },
34 | }
35 |
36 | app.Run(os.Args)
37 | }
38 |
--------------------------------------------------------------------------------
/examples/chat/README.md:
--------------------------------------------------------------------------------
1 | Browser-to-browser chat example
2 | ===============================
3 |
4 | This example uses https://github.com/nsf/bin2go to embed assets. To
5 | make sure it's installed, run
6 |
7 | go get github.com/nsf/bin2go
8 |
9 | Other neccessary packages
10 |
11 | go get -v github.com/gorilla/websocket
12 | go get -v github.com/tv42/birpc
13 | go get -v github.com/tv42/topic
14 |
15 | and put the resulting bin2go in `PATH`.
16 |
17 | To run the example:
18 |
19 | ./run
20 |
21 | or, to select another port:
22 |
23 | ./run -port=8888
24 |
25 | Then open two browser windows to http://localhost:8000/ , change your
26 | name from *J. Doe* to something more distinct, and type messages. See
27 | how they are transmitted to the other browser window.
28 |
29 | See the browser's Javascript console for a debug log of incoming and
30 | outgoing messages.
31 |
--------------------------------------------------------------------------------
/lib/bindings/session/session_cookie.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | /*
4 | Cookie
5 | source the source url
6 | name the cookie name
7 | value the cookie value
8 | domain the cookie domain
9 | path the cookie path
10 | creation the creation date
11 | expiry the expiration date
12 | last_access the last time the cookie was accessed
13 | secure is the cookie secure
14 | http_only is the cookie only valid for HTTP
15 | priority internal priority information
16 | */
17 | type Cookie struct {
18 | Source string `json:"source"`
19 | Name string `json:"name"`
20 | Value string `json:"value"`
21 | Domain string `json:"domain"`
22 | Path string `json:"path"`
23 | Creation int64 `json:"creation"`
24 | Expiry int64 `json:"expiry"`
25 | LastAccess int64 `json:"last_access"`
26 | Secure uint `json:"secure"`
27 | HttpOnly bool `json:"http_only"`
28 | Priority uint `json:"priority"`
29 | }
30 |
--------------------------------------------------------------------------------
/tutorials/basic_multiple_windows/basic_multiple_windows.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/miketheprogrammer/go-thrust/thrust"
5 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
6 | )
7 |
8 | func main() {
9 | thrust.InitLogger()
10 | // Set any Custom Provisioners before Start
11 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
12 | // thrust.Start() must always come before any bindings are created.
13 | thrust.Start()
14 |
15 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
16 | RootUrl: "http://breach.cc/",
17 | })
18 | thrustWindow.Show()
19 | thrustWindow.Maximize()
20 | thrustWindow.Focus()
21 |
22 | thrustWindow2 := thrust.NewWindow(thrust.WindowOptions{
23 | RootUrl: "http://google.com/",
24 | })
25 | thrustWindow2.Show()
26 | thrustWindow2.Focus()
27 |
28 | // BLOCKING - Dont run before youve excuted all commands you want first.
29 | thrust.LockThread()
30 | }
31 |
--------------------------------------------------------------------------------
/tutorials/advanced_window_devtools/advanced_window_devtools.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/miketheprogrammer/go-thrust/thrust"
7 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
8 | )
9 |
10 | func main() {
11 | thrust.InitLogger()
12 | // Set any Custom Provisioners before Start
13 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
14 | // thrust.Start() must always come before any bindings are created.
15 | thrust.Start()
16 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
17 | RootUrl: "http://breach.cc/",
18 | })
19 | thrustWindow.Show()
20 | thrustWindow.Maximize()
21 | thrustWindow.Focus()
22 |
23 | thrustWindow.OpenDevtools()
24 | // Lets do a window timeout
25 | go func() {
26 | <-time.After(time.Second * 10)
27 | thrustWindow.CloseDevtools()
28 | }()
29 | // BLOCKING - Dont run before youve excuted all commands you want first
30 | thrust.LockThread()
31 | }
32 |
--------------------------------------------------------------------------------
/tutorials/basic_window/basic_window.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/miketheprogrammer/go-thrust/thrust"
7 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
8 | )
9 |
10 | func main() {
11 |
12 | thrust.InitLogger()
13 | // Set any Custom Provisioners before Start
14 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
15 | // thrust.Start() must always come before any bindings are created.
16 | thrust.Start()
17 |
18 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
19 | RootUrl: "http://breach.cc/",
20 | })
21 | thrustWindow.Show()
22 | thrustWindow.Maximize()
23 | thrustWindow.Focus()
24 |
25 | // Lets do a window timeout
26 | go func() {
27 | <-time.After(time.Second * 5)
28 | thrustWindow.Close()
29 | thrust.Exit()
30 | }()
31 |
32 | // In lieu of something like an http server, we need to lock this thread
33 | // in order to keep it open, and keep the process running.
34 | // Dont worry we use runtime.Gosched :)
35 | thrust.LockThread()
36 | }
37 |
--------------------------------------------------------------------------------
/tutorials/basic_frameless_window/basic_frameless_window.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/miketheprogrammer/go-thrust/thrust"
5 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
6 | )
7 |
8 | func main() {
9 |
10 | thrust.InitLogger()
11 | // Set any Custom Provisioners before Start
12 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
13 | // thrust.Start() must always come before any bindings are created.
14 | thrust.Start()
15 |
16 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
17 | RootUrl: "http://breach.cc/",
18 | HasFrame: true,
19 | })
20 | thrustWindow.Show()
21 | thrustWindow.Focus()
22 |
23 | // Lets do a window timeout
24 | go func() {
25 | // <-time.After(time.Second * 5)
26 | // thrustWindow.Close()
27 | // thrust.Exit()
28 | }()
29 |
30 | // In lieu of something like an http server, we need to lock this thread
31 | // in order to keep it open, and keep the process running.
32 | // Dont worry we use runtime.Gosched :)
33 | thrust.LockThread()
34 | }
35 |
--------------------------------------------------------------------------------
/lib/spawn/unzip.go:
--------------------------------------------------------------------------------
1 | package spawn
2 |
3 | import (
4 | "archive/zip"
5 | "fmt"
6 | "io"
7 | "os"
8 | "path/filepath"
9 | )
10 |
11 | func unzip(src, dest string) error {
12 | r, err := zip.OpenReader(src)
13 | if err != nil {
14 | return err
15 | }
16 | defer r.Close()
17 | fmt.Println("Unzipping", src, "to", dest)
18 |
19 | for _, f := range r.File {
20 | rc, err := f.Open()
21 | if err != nil {
22 | return err
23 | }
24 | defer rc.Close()
25 |
26 | filePath := filepath.Join(dest, f.Name)
27 | if f.FileInfo().IsDir() {
28 | if err := os.MkdirAll(filePath, 0775); err != nil {
29 | return err
30 | }
31 | } else {
32 | if err := os.MkdirAll(filepath.Dir(filePath), 0775); err != nil {
33 | return err
34 | }
35 | file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.FileInfo().Mode())
36 | if err != nil {
37 | return err
38 | }
39 | defer file.Close()
40 |
41 | _, err = io.Copy(file, rc)
42 | if err != nil {
43 | return err
44 | }
45 | }
46 | }
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/tutorials/basic_session/basic_session.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/miketheprogrammer/go-thrust/thrust"
5 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
6 | )
7 |
8 | func main() {
9 | /*
10 | use basic setup
11 | */
12 | thrust.InitLogger()
13 | // Set any Custom Provisioners before Start
14 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
15 | // thrust.Start() must always come before any bindings are created.
16 | thrust.Start()
17 |
18 | /*
19 | Start of Basic Session Tutorial area
20 | */
21 | // arguments (incognito, useDisk)
22 | mysession := thrust.NewSession(false, false, "./cache")
23 | //mysession.SetInvokable(*session.NewDummySession())
24 | /*
25 | Modified basic_window, where we provide, a session argument
26 | to NewWindow.
27 | */
28 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
29 | RootUrl: "http://breach.cc/",
30 | Session: mysession,
31 | })
32 | thrustWindow.Show()
33 | thrustWindow.Maximize()
34 | thrustWindow.Focus()
35 |
36 | // BLOCKING - Dont run before youve excuted all commands you want first.
37 | thrust.LockThread()
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Michael Hernandez
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/tutorials/advanced_single_binary_distribution/advanced_single_binary_distribution.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/miketheprogrammer/go-thrust/thrust"
5 | "github.com/miketheprogrammer/go-thrust/tutorials/advanced_single_binary_distribution/provisioner"
6 | "github.com/pkg/profile"
7 | )
8 |
9 | func main() {
10 |
11 | thrust.InitLogger()
12 | // Set any Custom Provisioners before Start
13 | thrust.SetProvisioner(provisioner.NewSingleBinaryThrustProvisioner())
14 | // thrust.Start() must always come before any bindings are created.
15 | thrust.Start()
16 |
17 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
18 | RootUrl: "http://breach.cc/",
19 | HasFrame: true,
20 | })
21 | thrustWindow.Show()
22 | thrustWindow.Focus()
23 |
24 | // Lets do a window timeout
25 | go func() {
26 | // <-time.After(time.Second * 5)
27 | // thrustWindow.Close()
28 | // thrust.Exit()
29 | }()
30 |
31 | // In lieu of something like an http server, we need to lock this thread
32 | // in order to keep it open, and keep the process running.
33 | // Dont worry we use runtime.Gosched :)
34 | defer profile.Start().Stop()
35 | thrust.LockThread()
36 | }
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 | _vendor
10 |
11 | # Architecture specific extensions/prefixes
12 | *.[568vq]
13 | [568vq].out
14 |
15 | *.cgo1.go
16 | *.cgo2.c
17 | _cgo_defun.c
18 | _cgo_gotypes.go
19 | _cgo_export.*
20 |
21 | _testmain.go
22 |
23 | *.exe
24 | *.test
25 | *.prof
26 |
27 | thrust
28 | demo
29 |
30 | ThrustShell.app
31 | thrust-v0.7.0-linux-x64
32 |
33 | vendor/linux/x64/content_shell.pak
34 | vendor/linux/x64/icudtl.dat
35 | vendor/linux/x64/libchromiumcontent.so
36 | vendor/linux/x64/libffmpegsumo.so
37 | vendor/linux/x64/thrust_shell
38 |
39 | vendor/darwin/x64/ThrustShell.app
40 |
41 | release/go-thrust/vendor/linux/x64/content_shell.pak
42 | release/go-thrust/vendor/linux/x64/icudtl.dat
43 | release/go-thrust/vendor/linux/x64/libchromiumcontent.so
44 | release/go-thrust/vendor/linux/x64/libffmpegsumo.so
45 | release/go-thrust/go-thrust/vendor/linux/x64/thrust_shell
46 |
47 | release/vendor/darwin/x64/ThrustShell.app
48 |
49 | dist/*
50 |
51 | tutorial/bin/*
52 |
53 | release
54 |
55 | vendor
56 |
57 | dummy_session
58 |
59 | cache/*
60 |
61 | *cache*
--------------------------------------------------------------------------------
/examples/jankybrowser/browser.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "html/template"
7 | "log"
8 | "net/http"
9 |
10 | "github.com/miketheprogrammer/go-thrust/thrust"
11 | )
12 |
13 | var (
14 | port = flag.Int("port", 8000, "TCP port to listen on")
15 | )
16 | var html *template.Template = template.New("main")
17 |
18 | func init() {
19 | template.Must(html.New("browser.html").Parse(string(browser_html)))
20 |
21 | }
22 |
23 | func index(w http.ResponseWriter, req *http.Request) {
24 | w.Header().Set("Content-Type", "text/html")
25 | w.WriteHeader(http.StatusOK)
26 | err := html.ExecuteTemplate(w, "browser.html", nil)
27 | if err != nil {
28 | log.Printf("Template error: %v", err)
29 | }
30 | }
31 |
32 | func main() {
33 | flag.Parse()
34 | thrust.InitLogger()
35 | thrust.Start()
36 |
37 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
38 | RootUrl: fmt.Sprintf("http://127.0.0.1:%d", *port),
39 | })
40 | thrustWindow.Show()
41 | thrustWindow.Focus()
42 |
43 | addr := fmt.Sprintf("127.0.0.1:%d", *port)
44 | http.Handle("/", http.HandlerFunc(index))
45 | err := http.ListenAndServe(addr, nil)
46 |
47 | if err != nil {
48 | panic(err)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/examples/chat/server/chat.css:
--------------------------------------------------------------------------------
1 | #msg {
2 | border: 1px solid gray;
3 | padding: 0.5em;
4 | max-height: 80%;
5 | }
6 |
7 | #msg p {
8 | margin: 0.1em;
9 | }
10 |
11 | #msg span {
12 | margin-right: 0.5em;
13 | }
14 |
15 | #msg .time {
16 | font-family: monospace;
17 | font-size: 80%;
18 | color: #404040;
19 | float: right;
20 | }
21 |
22 | #msg .from {
23 | color: blue;
24 | }
25 |
26 | #msg .from:after {
27 | content: ":";
28 | }
29 |
30 | #msg .message {
31 | }
32 |
33 | form[name="msgform"] {
34 | max-width: 95%;
35 | display: -webkit-flex;
36 | display: -moz-flex;
37 | display: -ms-flex;
38 | display: flex;
39 | -webkit-flex-flow: row nowrap;
40 | -moz-flex-flow: row nowrap;
41 | -ms-flex-flow: row nowrap;
42 | -ms-flex-direction: row;
43 | -ms-flex-wrap: nowrap;
44 | flex-flow: row nowrap;
45 | }
46 |
47 | form[name="msgform"] input {
48 | -webkit-flex: 1 auto;
49 | -moz-flex: 1 auto;
50 | -ms-flex: 1 auto;
51 | flex: 1 auto;
52 | }
53 |
54 | form[name="msgform"] input[name="name"] {
55 | max-width: 8em;
56 | }
57 |
58 | form[name="msgform"] input[type="submit"] {
59 | -webkit-flex: 0 0 auto;
60 | -moz-flex: 0 0 auto;
61 | -ms-flex: 0 0 auto;
62 | flex: 0 0 auto;
63 | }
64 |
--------------------------------------------------------------------------------
/tutorials/advanced_single_binary_distribution/provisioner/single_binary_thrust_provisioner.go:
--------------------------------------------------------------------------------
1 | package provisioner
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 |
8 | "github.com/miketheprogrammer/go-thrust/lib/spawn"
9 | )
10 |
11 | /*
12 | Single Binary Distribution
13 | */
14 | type SingleBinaryThrustProvisioner struct{}
15 |
16 | /*
17 | NewSingleBinaryThrustProvisioner instantiates an SingleBinaryThrustProvisioner
18 |
19 | go:generate go-bindata -pkg $GOPACKAGE -o vendor.go vendor/
20 | */
21 | func NewSingleBinaryThrustProvisioner() SingleBinaryThrustProvisioner {
22 | return SingleBinaryThrustProvisioner{}
23 | }
24 |
25 | /*
26 | Provisions a thrust environment based on settings.
27 | */
28 | func (sbtp SingleBinaryThrustProvisioner) Provision() error {
29 | err := sbtp.extractToPath(spawn.GetDownloadPath())
30 | if err != nil {
31 | return err
32 | }
33 | return spawn.Bootstrap()
34 | }
35 |
36 | func (sbtp SingleBinaryThrustProvisioner) extractToPath(filepath string) error {
37 | data, err := Asset("vendor/thrust-v0.7.6-darwin-x64.zip")
38 | if err != nil {
39 | fmt.Println("Error accessing thrust bindata")
40 | return err
41 | }
42 | fmt.Println("No error accessing thrust bindata")
43 | fmt.Println("Writing to", filepath)
44 | err = ioutil.WriteFile(filepath, data, os.ModePerm)
45 | if nil != err {
46 | return err
47 | }
48 | return nil
49 | // return spawn.UnzipExecutable(filepath)
50 | }
51 |
--------------------------------------------------------------------------------
/lib/bindings/menu/menuitem.go:
--------------------------------------------------------------------------------
1 | package menu
2 |
3 | import . "github.com/miketheprogrammer/go-thrust/lib/common"
4 |
5 | type MenuItem struct {
6 | CommandID uint `json:"command_id,omitempty"`
7 | Label string `json:"label,omitempty"`
8 | GroupID uint `json:"group_id,omitempty"`
9 | SubMenu *Menu `json:"submenu,omitempty"`
10 | Type string `json:"type,omitempty"`
11 | Checked bool `json:"checked"`
12 | Enabled bool `json:"enabled"`
13 | Visible bool `json:"visible"`
14 | Parent *Menu `json:"-"`
15 | }
16 |
17 | func NewMenuItem() *MenuItem {
18 | return &MenuItem{}
19 | }
20 |
21 | func (mi MenuItem) IsSubMenu() bool {
22 | return mi.SubMenu != nil
23 | }
24 |
25 | func (mi MenuItem) IsCheckItem() bool {
26 | return mi.Type == "check"
27 | }
28 |
29 | func (mi MenuItem) IsRadioItem() bool {
30 | return mi.Type == "radio"
31 | }
32 |
33 | func (mi MenuItem) IsGroupID(groupID uint) bool {
34 | return mi.GroupID == groupID
35 | }
36 |
37 | func (mi MenuItem) IsCommandID(commandID uint) bool {
38 | return mi.CommandID == commandID
39 | }
40 |
41 | func (mi MenuItem) HandleEvent() {
42 | Log.Print("EventType", mi.Type)
43 | switch mi.Type {
44 | case "check":
45 | Log.Print("Toggling Checked(", mi.Checked, ")", "to", "checked(", !mi.Checked, ")")
46 | mi.Parent.SetChecked(mi.CommandID, !mi.Checked)
47 | case "radio":
48 | Log.Print("Toggling RadioChecked(", mi.Checked, ")", "to", "checked(", !mi.Checked, ")")
49 | mi.Parent.ToggleRadio(mi.CommandID, mi.GroupID, !mi.Checked)
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/examples/jankybrowser/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var argv = require('minimist')(process.argv.slice(2));
4 |
5 | var async = require('async');
6 |
7 | var api = null;
8 | var window = null;
9 |
10 | var url = __dirname + "/test.html";
11 |
12 | var fs = require("fs");
13 | var http = require("http");
14 | var port = 21024;
15 |
16 |
17 | async.series([
18 | function(cb) {
19 | var server = http.createServer(function(req, res) {
20 | if(req.url === "/") {
21 | req.url = "/browser.html";
22 | }
23 |
24 | req.url = __dirname + req.url;
25 | console.log("attempting to load %s", req.url);
26 | fs.createReadStream(req.url).pipe(res);
27 | });
28 |
29 | server.listen(port, '127.0.0.1', function() {
30 | console.log("listening on %s", port);
31 | cb(null);
32 | });
33 | },
34 | function(cb_) {
35 | require('node-thrust')(function(err, a) {
36 | api = a;
37 | return cb_(err);
38 | }, argv.thrust_path || null);
39 | },
40 | function(cb_) {
41 | var root = "http://127.0.0.1:" + port;
42 |
43 | if(argv.ext) root += ("/" + argv.ext);
44 |
45 | window = api.window({
46 | root_url: root,
47 | size: {
48 | width: 1024,
49 | height: 768
50 | }
51 | });
52 |
53 | window.on("closed", function() {
54 | process.exit(0);
55 | });
56 |
57 | return cb_();
58 | },
59 | function(cb_) {
60 | window.show(cb_);
61 | }
62 | ], function(err) {
63 | if(err) {
64 | console.log('FAILED');
65 | console.error(err);
66 | }
67 | console.log('OK');
68 | });
69 |
--------------------------------------------------------------------------------
/tutorials/basic_webserver_app/basic_webserver_app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/miketheprogrammer/go-thrust/thrust"
8 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
9 | )
10 |
11 | func handler(w http.ResponseWriter, r *http.Request) {
12 | fmt.Fprint(w, htmlIndex)
13 | }
14 |
15 | func main() {
16 | http.HandleFunc("/", handler)
17 | thrust.InitLogger()
18 | // Set any Custom Provisioners before Start
19 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
20 | // thrust.Start() must always come before any bindings are created.
21 | thrust.Start()
22 |
23 | mysession := thrust.NewSession(false, false, "cache")
24 |
25 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
26 | RootUrl: "http://localhost:8080/",
27 | Session: mysession,
28 | })
29 | thrustWindow.Show()
30 | thrustWindow.Maximize()
31 | thrustWindow.Focus()
32 |
33 | // See, we dont use thrust.LockThread() because we now have something holding the process open
34 | http.ListenAndServe(":8080", nil)
35 | }
36 |
37 | var htmlIndex string = `
38 |
39 |
40 | Welcome to Go-Thrust
41 |
42 |
52 |
53 |
54 | `
55 |
--------------------------------------------------------------------------------
/lib/bindings/session/session_invokable.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import "github.com/miketheprogrammer/go-thrust/lib/commands"
4 |
5 | /*
6 | Methods prefixed with Invoke are methods that can be called by ThrustCore, this differs to our
7 | standard call/reply, or event actions, since we are now the responder.
8 | */
9 | /*
10 | SessionInvokable is an interface designed to allow you to create your own Session Store.
11 | Simple build a structure that supports these methods, and call session.SetInvokable(myInvokable)
12 |
13 | ?? What is the best and most simple value to return from these methods. Assume most of them work
14 | on the principle of single value return. Do we worry about different types or do we use a CommandResponse object
15 | If we use different types, we will just have to add them to a CommandResponse from the Caller anyway.
16 | I would say different types will keep the user from shooting himself in the foot.
17 | */
18 |
19 | type SessionInvokable interface {
20 | InvokeCookiesLoad(args *commands.CommandResponseArguments, session *Session) (cookies []Cookie)
21 | InvokeCookiesLoadForKey(args *commands.CommandResponseArguments, session *Session) (cookies []Cookie)
22 | InvokeCookiesFlush(args *commands.CommandResponseArguments, session *Session) bool
23 | InvokeCookiesAdd(args *commands.CommandResponseArguments, session *Session) bool
24 | InvokeCookiesUpdateAccessTime(args *commands.CommandResponseArguments, session *Session) bool
25 | InvokeCookiesDelete(args *commands.CommandResponseArguments, session *Session) bool
26 | InvokeCookieForceKeepSessionState(args *commands.CommandResponseArguments, session *Session)
27 | }
28 |
--------------------------------------------------------------------------------
/lib/events/eventhandler.go:
--------------------------------------------------------------------------------
1 | package events
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/miketheprogrammer/go-thrust/lib/commands"
7 | "github.com/miketheprogrammer/go-thrust/lib/dispatcher"
8 | )
9 |
10 | /*
11 | Create a new EventHandler for a give event.
12 | */
13 | func NewHandler(event string, fn interface{}) (ThrustEventHandler, error) {
14 | h := ThrustEventHandler{}
15 | h.Event = event
16 | h.Type = "event"
17 | err := h.SetHandleFunc(fn)
18 | dispatcher.RegisterHandler(h)
19 | return h, err
20 | }
21 |
22 | /**
23 | Begin Thrust Handler Code.
24 | **/
25 | type Handler interface {
26 | Handle(cr commands.CommandResponse)
27 | Register()
28 | SetHandleFunc(fn interface{})
29 | }
30 |
31 | type ThrustEventHandler struct {
32 | Type string
33 | Event string
34 | Handler interface{}
35 | }
36 |
37 | func (teh ThrustEventHandler) Handle(cr commands.CommandResponse) {
38 | if cr.Action != "event" {
39 | return
40 | }
41 | if cr.Type != teh.Event && teh.Event != "*" {
42 | return
43 | }
44 | cr.Event.Type = cr.Type
45 | if fn, ok := teh.Handler.(func(commands.CommandResponse)); ok == true {
46 | fn(cr)
47 | return
48 | }
49 | if fn, ok := teh.Handler.(func(commands.EventResult)); ok == true {
50 | fn(cr.Event)
51 | return
52 | }
53 | }
54 |
55 | func (teh *ThrustEventHandler) SetHandleFunc(fn interface{}) error {
56 | if fn, ok := fn.(func(commands.CommandResponse)); ok == true {
57 | teh.Handler = fn
58 | return nil
59 | }
60 | if fn, ok := fn.(func(commands.EventResult)); ok == true {
61 | teh.Handler = fn
62 | return nil
63 | }
64 |
65 | return errors.New("Invalid Handler Definition")
66 | }
67 |
--------------------------------------------------------------------------------
/lib/spawn/download.go:
--------------------------------------------------------------------------------
1 | package spawn
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "os"
9 | "strings"
10 | "time"
11 |
12 | "github.com/cheggaaa/pb"
13 |
14 | . "github.com/miketheprogrammer/go-thrust/lib/common"
15 | )
16 |
17 | func downloadFromUrl(url, filepath, version string) (fileName string, err error) {
18 | url = strings.Replace(url, "$V", version, 2)
19 | fileName = strings.Replace(filepath, "$V", version, 1)
20 | if PathNotExist(fileName) == false {
21 | fmt.Println("Thrust already exists on filesystem .... skipping")
22 | return
23 | }
24 | fmt.Println("Extract directory was", GetDownloadPath())
25 | fmt.Println("Downloading", url, "to", fileName)
26 |
27 | output, err := os.Create(fileName)
28 | if err != nil {
29 | Log.Print("Error while creating", fileName, "-", err)
30 | return
31 | }
32 | defer output.Close()
33 |
34 | response, err := http.Get(url)
35 | if err != nil {
36 | fmt.Println("Error while downloading", url, "-", err)
37 | return
38 | }
39 | defer response.Body.Close()
40 | if response.StatusCode != http.StatusOK {
41 | err = errors.New(fmt.Sprintf("Server return non-200 status: %v", response.Status))
42 | fmt.Println(err)
43 | return
44 | }
45 |
46 | // create bar
47 | bar := pb.New(int(response.ContentLength)).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10)
48 | bar.ShowSpeed = true
49 | bar.Start()
50 | defer bar.Finish()
51 |
52 | // create multi writer
53 | writer := io.MultiWriter(output, bar)
54 |
55 | _, err = io.Copy(writer, response.Body)
56 | if err != nil {
57 | Log.Print("Error while downloading", url, "-", err)
58 | return
59 | }
60 |
61 | return
62 | }
63 |
--------------------------------------------------------------------------------
/lib/dispatcher/dispatcher.go:
--------------------------------------------------------------------------------
1 | package dispatcher
2 |
3 | import (
4 | "runtime"
5 |
6 | "github.com/miketheprogrammer/go-thrust/lib/commands"
7 | "github.com/miketheprogrammer/go-thrust/lib/connection"
8 | )
9 |
10 | type HandleFunc func(commands.CommandResponse)
11 | type Handler interface {
12 | Handle(commands.CommandResponse)
13 | }
14 |
15 | var registry []interface{}
16 |
17 | /*
18 | RegisterHandler registers a HandleFunc f to receive a CommandResponse when one is sent to the system.
19 | */
20 | func RegisterHandler(h interface{}) {
21 | registry = append(registry, h)
22 | }
23 |
24 | /*
25 | Dispatch dispatches a CommandResponse to every handler in the registry
26 | */
27 | func Dispatch(command commands.CommandResponse) {
28 | for _, f := range registry {
29 | if fn, ok := f.(func(cr commands.CommandResponse)); ok == true {
30 | go fn(command)
31 | }
32 | if handler, ok := f.(Handler); ok == true {
33 | go handler.Handle(command)
34 | }
35 | }
36 | }
37 |
38 | /*
39 | RunLoop starts a loop that receives CommandResponses and dispatches them.
40 | This is a helper method, but you could just implement your own, if you only
41 | need this loop to be the blocking loop.
42 | For Instance, in a HTTP Server setting, you might want to run this as a
43 | goroutine and then let the servers blocking handler keep the process open.
44 | As long as there are commands in the channel, this loop will dispatch as fast
45 | as possible
46 | */
47 | func RunLoop() {
48 | outChannels := connection.GetOutputChannels()
49 | defer connection.Clean()
50 |
51 | for {
52 | Run(outChannels)
53 | runtime.Gosched()
54 | }
55 |
56 | }
57 |
58 | func Run(outChannels *connection.Out) {
59 | response := <-outChannels.CommandResponses
60 | Dispatch(response)
61 | }
62 |
--------------------------------------------------------------------------------
/thrust/thrust_test.go:
--------------------------------------------------------------------------------
1 | package thrust
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/miketheprogrammer/go-thrust/lib/commands"
7 | )
8 |
9 | func TestNewEventHandler(t *testing.T) {
10 | handler, err := NewEventHandler("focus", func(cr commands.CommandResponse) {
11 |
12 | })
13 |
14 | if err != nil {
15 | t.Fail()
16 | }
17 | if handler.Type != "event" {
18 | t.Fail()
19 | }
20 | if handler.Event != "focus" {
21 | t.Fail()
22 | }
23 |
24 | handler, err = NewEventHandler("focus", func(rr commands.ReplyResult) {
25 |
26 | })
27 |
28 | if err == nil {
29 | t.Fail()
30 | }
31 |
32 | handler, err = NewEventHandler("focus", func(er commands.EventResult) {
33 |
34 | })
35 |
36 | if err != nil {
37 | t.Fail()
38 | }
39 | if handler.Type != "event" {
40 | t.Fail()
41 | }
42 | if handler.Event != "focus" {
43 | t.Fail()
44 | }
45 | }
46 |
47 | func TestHandlerHandle(t *testing.T) {
48 | var responses int
49 |
50 | handler, err := NewEventHandler("focus", func(cr commands.CommandResponse) {
51 | t.Log("Woot")
52 | t.Log(cr)
53 | responses += 1
54 | })
55 |
56 | if err != nil {
57 | t.Fail()
58 | }
59 |
60 | handler.Handle(commands.CommandResponse{
61 | Action: "event",
62 | ID: 1,
63 | Type: "focus",
64 | Event: commands.EventResult{
65 | Type: "focus",
66 | },
67 | })
68 |
69 | handler, err = NewEventHandler("focus", func(er commands.EventResult) {
70 | t.Log("Woot")
71 | t.Log(er)
72 | responses += 1
73 | })
74 |
75 | if err != nil {
76 | t.Fail()
77 | }
78 |
79 | handler.Handle(commands.CommandResponse{
80 | Action: "event",
81 | ID: 1,
82 | Type: "focus",
83 | Event: commands.EventResult{
84 | Type: "focus",
85 | },
86 | })
87 |
88 | if responses != 2 {
89 | t.Fail()
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/lib/bindings/session/dummy_session.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import (
4 | "github.com/miketheprogrammer/go-thrust/lib/commands"
5 | "github.com/miketheprogrammer/go-thrust/lib/common"
6 | )
7 |
8 | type DummySession struct{}
9 |
10 | func NewDummySession() (dummy *DummySession) {
11 | return &DummySession{}
12 | }
13 |
14 | /*
15 | For Simplicity type declarations
16 | */
17 | func (ds DummySession) InvokeCookiesLoad(args *commands.CommandResponseArguments, session *Session) (cookies []Cookie) {
18 | common.Log.Print("InvokeCookiesLoad")
19 | cookies = make([]Cookie, 0)
20 |
21 | return cookies
22 | }
23 |
24 | func (ds DummySession) InvokeCookiesLoadForKey(args *commands.CommandResponseArguments, session *Session) (cookies []Cookie) {
25 | common.Log.Print("InvokeCookiesLoadForKey")
26 | cookies = make([]Cookie, 0)
27 |
28 | return cookies
29 | }
30 |
31 | func (ds DummySession) InvokeCookiesFlush(args *commands.CommandResponseArguments, session *Session) bool {
32 | common.Log.Print("InvokeCookiesFlush")
33 | return false
34 | }
35 |
36 | func (ds DummySession) InvokeCookiesAdd(args *commands.CommandResponseArguments, session *Session) bool {
37 | common.Log.Print("InvokeCookiesAdd")
38 | return false
39 | }
40 |
41 | func (ds DummySession) InvokeCookiesUpdateAccessTime(args *commands.CommandResponseArguments, session *Session) bool {
42 | common.Log.Print("InvokeCookiesUpdateAccessTime")
43 | return false
44 | }
45 |
46 | func (ds DummySession) InvokeCookiesDelete(args *commands.CommandResponseArguments, session *Session) bool {
47 | common.Log.Print("InvokeCookiesDelete")
48 | return false
49 | }
50 |
51 | func (ds DummySession) InvokeCookieForceKeepSessionState(args *commands.CommandResponseArguments, session *Session) {
52 | common.Log.Print("InvokeCookieForceKeepSessionState")
53 | }
54 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | Roadmap to v1.0 :
2 | ================
3 | Please note Complete Support *may* not be toggled until Thrust core is stable.
4 |
5 | - [ ] Add kiosk support (core.0.7.3)
6 | - [X] Queue Requests prior to Object being created to matain a synchronous looking API without the need for alot of state checks
7 | - [ ] Remove overuse of pointers in structs where modification will not take place
8 | - [ ] Add Window Support
9 | - [X] Basic Support
10 | - [X] Refactor Connection usage
11 | - [ ] Complete Support
12 | - Accessors (core.0.7.3)
13 | - Events (core.0.7.3)
14 |
15 | - [ ] Add Menu Support
16 | - [X] Basic Support
17 | - [X] Refactor Connection usage
18 | - [ ] Complete Support
19 |
20 | - [ ] Add Session Support
21 | - [X] Basic Support
22 | - [ ] Complete Support
23 |
24 | - [X] Implement Package Connection
25 |
26 | - [x] Seperate out in to packages other than main
27 | - [X] Package Window
28 | - [X] Package Menu
29 | - [X] Package Commands
30 | - [X] Package Spawn
31 |
32 | - [ ] Remove func Main as this is a Library
33 | - [ ] Should use Tests instead
34 |
35 | - [X] Refactor how Dispatching occurs
36 | - [X] We should not have to manually dispatch, there should be a registration method
37 |
38 | - [ ] Refactor menu.SetChecked to accept a nillable menu item pointer, so we dont have to waste resources finding the item in the Tree
39 |
40 | - [X] Refactor CallWhen* methods, Due to the nature of using GoRoutines, there is the chance that calls will execute out of the original order they were intended.
41 |
42 | - [X] Create a script to autodownload binaries
43 |
44 | - [X] Refactor Logging
45 |
46 | - [ ] SubMenus need order preservation
47 |
48 | - [X] vendor folders need versioning
49 |
50 | - [X] Need to fix Pathing for autoinstall and autorun. Relative paths will not work for most use cases.
--------------------------------------------------------------------------------
/lib/spawn/helper_linux.go:
--------------------------------------------------------------------------------
1 | package spawn
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strings"
7 | )
8 |
9 | /*
10 | GetThrustDirectory returns the Directory where the unzipped thrust contents are.
11 | Differs between builds based on OS
12 | */
13 | func GetThrustDirectory() string {
14 | return filepath.Join(base, "vendor", "linux", "x64", thrustVersion)
15 | }
16 |
17 | /*
18 | GetDownloadDirectory gets the download or extract directory for Thrust
19 | */
20 | func GetDownloadPath() string {
21 | return strings.Replace(filepath.Join(base, "$V"), "$V", thrustVersion, 1)
22 | }
23 |
24 | /*
25 | GetExecutablePath returns the path to the Thrust Executable
26 | Differs between builds based on OS
27 | */
28 | func GetExecutablePath() string {
29 | return GetThrustDirectory() + "/thrust_shell"
30 | }
31 |
32 | /*
33 | GetDownloadURL returns the interpolatable version of the Thrust download url
34 | Differs between builds based on OS
35 | */
36 | func GetDownloadURL() string {
37 | return "https://github.com/breach/thrust/releases/download/v$V/thrust-v$V-linux-x64.zip"
38 | }
39 |
40 | /*
41 | Bootstrap executes the default bootstrapping plan for this system and returns an error if failed
42 | */
43 | func Bootstrap() error {
44 | if executableNotExist() == true {
45 | return prepareExecutable()
46 | }
47 | return nil
48 | }
49 |
50 | func executableNotExist() bool {
51 | _, err := os.Stat(GetExecutablePath())
52 | return os.IsNotExist(err)
53 | }
54 |
55 | func prepareExecutable() error {
56 | path, err := downloadFromUrl(GetDownloadURL(), base+"/$V", thrustVersion)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | return UnzipExecutable(path)
62 | }
63 |
64 | func UnzipExecutable(path string) error {
65 | return unzip(path, GetThrustDirectory())
66 | }
67 |
68 | func PathNotExist(path string) bool {
69 | _, err := os.Stat(path)
70 | return os.IsNotExist(err)
71 | }
72 |
--------------------------------------------------------------------------------
/lib/spawn/helper_windows.go:
--------------------------------------------------------------------------------
1 | package spawn
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strings"
7 | )
8 |
9 | /*
10 | GetThrustDirectory returns the Directory where the unzipped thrust contents are.
11 | Differs between builds based on OS
12 | */
13 | func GetThrustDirectory() string {
14 | return filepath.Join(base, "vendor", "windows", "ia32", thrustVersion)
15 | }
16 |
17 | /*
18 | GetDownloadDirectory gets the download or extract directory for Thrust
19 | */
20 | func GetDownloadPath() string {
21 | return strings.Replace(filepath.Join(base, "$V"), "$V", thrustVersion, 1)
22 | }
23 |
24 | /*
25 | GetExecutablePath returns the path to the Thrust Executable
26 | Differs between builds based on OS
27 | */
28 | func GetExecutablePath() string {
29 | return filepath.Join(GetThrustDirectory(), "thrust_shell.exe")
30 | }
31 |
32 | /*
33 | GetDownloadURL returns the interpolatable version of the Thrust download url
34 | Differs between builds based on OS
35 | */
36 | func GetDownloadURL() string {
37 | //https://github.com/breach/thrust/releases/download/v0.7.5/thrust-v0.7.5-win32-ia32.zip
38 | return "https://github.com/breach/thrust/releases/download/v$V/thrust-v$V-win32-ia32.zip"
39 | }
40 |
41 | func Bootstrap() error {
42 | if executableNotExist() == true {
43 | if err := prepareExecutable(); err != nil {
44 | return err
45 | }
46 | return nil
47 | }
48 | return nil
49 | }
50 |
51 | func executableNotExist() bool {
52 | _, err := os.Stat(GetExecutablePath())
53 | return os.IsNotExist(err)
54 | }
55 |
56 | func prepareExecutable() error {
57 | path, err := downloadFromUrl(GetDownloadURL(), base+"\\$V", thrustVersion)
58 | if err != nil {
59 | return err
60 | }
61 |
62 | return UnzipExecutable(path)
63 | }
64 |
65 | func UnzipExecutable(path string) error {
66 | return unzip(path, GetThrustDirectory())
67 | }
68 |
69 | func PathNotExist(path string) bool {
70 | _, err := os.Stat(path)
71 | return os.IsNotExist(err)
72 | }
73 |
--------------------------------------------------------------------------------
/tutorials/basic_menu/basic_menu.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/miketheprogrammer/go-thrust/thrust"
5 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
6 | )
7 |
8 | func main() {
9 | thrust.InitLogger()
10 | // Set any Custom Provisioners before Start
11 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
12 | // thrust.Start() must always come before any bindings are created.
13 | thrust.Start()
14 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
15 | RootUrl: "http://breach.cc/",
16 | })
17 | thrustWindow.Show()
18 | thrustWindow.Maximize()
19 | thrustWindow.Focus()
20 | // make our top menus
21 | //applicationMenu, is essentially the menu bar
22 | applicationMenu := thrust.NewMenu()
23 | //applicationMenuRoot is the first menu, on darwin this is always named the name of your application.
24 | applicationMenuRoot := thrust.NewMenu()
25 | //File menu is our second menu
26 | fileMenu := thrust.NewMenu()
27 |
28 | // Lets build our root menu.
29 | // the first argument to AddItem is a CommandID
30 | // A CommandID is used by Thrust Core to communicate back results and events.
31 | applicationMenuRoot.AddItem(1, "About")
32 | // Now for the File menu
33 | fileMenu.AddItem(2, "Open")
34 | fileMenu.AddItem(3, "Edit")
35 | fileMenu.AddSeparator()
36 | fileMenu.AddItem(4, "Close")
37 |
38 | // Now we just need to plumb our menus together any way we want.
39 |
40 | applicationMenu.AddSubmenu(5, "Application", applicationMenuRoot)
41 | applicationMenu.AddSubmenu(6, "File", fileMenu)
42 |
43 | // Remember how in basic_browser, Window automatically self registered with the dispatcher.
44 | // unfortunately we have no such luck here.
45 | // I suppose this method could be added as an effect of SetApplicationMenu, but the effects of that need to be
46 | // Ironed out.
47 | // However, as least we only need to register the top level menu for events, all sub menus will delegate for the top menu.
48 |
49 | // Now we set it as our application Menu
50 | applicationMenu.SetApplicationMenu()
51 | // BLOCKING - Dont run before youve excuted all commands you want first.
52 | thrust.LockThread()
53 | }
54 |
--------------------------------------------------------------------------------
/tutorials/basic_remote_messaging/basic_remote_messaging.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/miketheprogrammer/go-thrust/lib/bindings/window"
8 | "github.com/miketheprogrammer/go-thrust/lib/commands"
9 | "github.com/miketheprogrammer/go-thrust/thrust"
10 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
11 | )
12 |
13 | func handler(w http.ResponseWriter, r *http.Request) {
14 | fmt.Fprint(w, htmlIndex)
15 | }
16 |
17 | func main() {
18 | http.HandleFunc("/", handler)
19 | thrust.InitLogger()
20 | // Set any Custom Provisioners before Start
21 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
22 | // thrust.Start() must always come before any bindings are created.
23 | thrust.Start()
24 |
25 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
26 | RootUrl: "http://localhost:8080/",
27 | })
28 | thrustWindow.Show()
29 | thrustWindow.Maximize()
30 | thrustWindow.Focus()
31 | thrustWindow.OpenDevtools()
32 | _, err := thrustWindow.HandleRemote(func(er commands.EventResult, this *window.Window) {
33 | fmt.Println("RemoteMessage Recieved:", er.Message.Payload)
34 | // Keep in mind once we have the message, lets say its json of some new type we made,
35 | // We can unmarshal it to that type.
36 | // Same goes for the other way around.
37 | this.SendRemoteMessage("boop")
38 | })
39 | if err != nil {
40 | fmt.Println(err)
41 | thrust.Exit()
42 | }
43 | // See, we dont use thrust.LockThread() because we now have something holding the process open
44 | http.ListenAndServe(":8080", nil)
45 | }
46 |
47 | var htmlIndex string = `
48 |
49 |
50 |
68 |
69 |
70 |
71 |
72 |
73 | `
74 |
--------------------------------------------------------------------------------
/examples/chat/LICENSE:
--------------------------------------------------------------------------------
1 | # The primary Copyright holder of most of the code is Tommi Virtanen
2 |
3 | Copyright (c) 2013 Tommi Virtanen
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 |
23 | # The Secondary Copyright holder is Michael Hernandez, whom has made this into a derivative work.
24 |
25 | Copyright (c) 2014 Michael Hernandez
26 |
27 | Permission is hereby granted, free of charge, to any person obtaining a copy
28 | of this software and associated documentation files (the "Software"), to deal
29 | in the Software without restriction, including without limitation the rights
30 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31 | copies of the Software, and to permit persons to whom the Software is
32 | furnished to do so, subject to the following conditions:
33 |
34 | The above copyright notice and this permission notice shall be included in
35 | all copies or substantial portions of the Software.
36 |
37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
43 | THE SOFTWARE.
--------------------------------------------------------------------------------
/tutorials/basic_menu_events/basic_menu_events.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/miketheprogrammer/go-thrust/lib/bindings/menu"
7 | "github.com/miketheprogrammer/go-thrust/lib/commands"
8 | "github.com/miketheprogrammer/go-thrust/thrust"
9 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
10 | )
11 |
12 | func main() {
13 | thrust.InitLogger()
14 | // Set any Custom Provisioners before Start
15 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
16 | // thrust.Start() must always come before any bindings are created.
17 | thrust.Start()
18 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
19 | RootUrl: "http://breach.cc/",
20 | })
21 | thrustWindow.Show()
22 | thrustWindow.Maximize()
23 | thrustWindow.Focus()
24 |
25 | // make our top menus
26 | //applicationMenu, is essentially the menu bar
27 | applicationMenu := thrust.NewMenu()
28 | //applicationMenuRoot is the first menu, on darwin this is always named the name of your application.
29 | applicationMenuRoot := thrust.NewMenu()
30 | //File menu is our second menu
31 | fileMenu := thrust.NewMenu()
32 |
33 | // Lets build our root menu.
34 | // the first argument to AddItem is a CommandID
35 | // A CommandID is used by Thrust Core to communicate back results and events.
36 | applicationMenuRoot.AddItem(1, "About")
37 | applicationMenuRoot.RegisterEventHandlerByCommandID(1,
38 | func(reply commands.CommandResponse, item *menu.MenuItem) {
39 | fmt.Println("About Handled")
40 | })
41 | // Now for the File menu
42 | fileMenu.AddItem(2, "Open")
43 | fileMenu.RegisterEventHandlerByCommandID(2,
44 | func(reply commands.CommandResponse, item *menu.MenuItem) {
45 | fmt.Println("Open Handled")
46 | })
47 | fileMenu.AddItem(3, "Edit")
48 | fileMenu.AddSeparator()
49 | fileMenu.AddItem(4, "Close")
50 | fileMenu.RegisterEventHandlerByCommandID(4,
51 | func(reply commands.CommandResponse, item *menu.MenuItem) {
52 | fmt.Println("Close Event Handled")
53 | thrust.Exit()
54 | })
55 | // Now we just need to plumb our menus together any way we want.
56 |
57 | applicationMenu.AddSubmenu(5, "Application", applicationMenuRoot)
58 | applicationMenu.AddSubmenu(6, "File", fileMenu)
59 |
60 | // Remember how in basic_browser, Window automatically self registered with the dispatcher.
61 | // unfortunately we have no such luck here.
62 | // I suppose this method could be added as an effect of SetApplicationMenu, but the effects of that need to be
63 | // Ironed out.
64 | // However, as least we only need to register the top level menu for events, all sub menus will delegate for the top menu.
65 |
66 | // Now we set it as our application Menu
67 | applicationMenu.SetApplicationMenu()
68 | // BLOCKING - Dont run before youve excuted all commands you want first.
69 | thrust.LockThread()
70 | }
71 |
--------------------------------------------------------------------------------
/thrust/thrust.go:
--------------------------------------------------------------------------------
1 | package thrust
2 |
3 | import (
4 | "runtime"
5 | "time"
6 |
7 | "github.com/miketheprogrammer/go-thrust/lib/bindings/menu"
8 | "github.com/miketheprogrammer/go-thrust/lib/bindings/session"
9 | "github.com/miketheprogrammer/go-thrust/lib/bindings/window"
10 | "github.com/miketheprogrammer/go-thrust/lib/common"
11 | "github.com/miketheprogrammer/go-thrust/lib/connection"
12 | "github.com/miketheprogrammer/go-thrust/lib/dispatcher"
13 | "github.com/miketheprogrammer/go-thrust/lib/events"
14 | "github.com/miketheprogrammer/go-thrust/lib/spawn"
15 | )
16 |
17 | /*
18 | Begin Generic Access and Binding Management Section.
19 | */
20 |
21 | /*
22 | Bindings
23 | */
24 |
25 | type WindowOptions window.Options
26 |
27 | /* NewWindow creates a new Window Binding */
28 | func NewWindow(options WindowOptions) *window.Window {
29 | return window.NewWindow(window.Options(options))
30 | }
31 |
32 | /* NewSession creates a new Session Binding */
33 | func NewSession(incognito, overrideDefaultSession bool, path string) *session.Session {
34 | return session.NewSession(incognito, overrideDefaultSession, path)
35 | }
36 |
37 | /* NewMenu creates a new Menu Binding */
38 | func NewMenu() *menu.Menu {
39 | return menu.NewMenu()
40 | }
41 |
42 | /*
43 | Start spawns the thrust core executable, and begins the dispatcher loop in a go routine
44 | */
45 | func Start() {
46 | spawn.Run()
47 | go dispatcher.RunLoop()
48 | }
49 |
50 | /*
51 | SetProvisioner overrides the default Provisioner, the default provisioner downloads
52 | Thrust-Core if Thrust-Core is not found.
53 | It also does some other nifty things to configure your install (on darwin) for the ApplicationName you choose.
54 | */
55 | func SetProvisioner(p spawn.Provisioner) {
56 | spawn.SetProvisioner(p)
57 | }
58 |
59 | /*
60 | Use LockThread on the main thread in lieue of a webserver or some other service that holds the thread
61 | This is primarily used when Thrust and just Thrust is what you are using, in that case lock the thread.
62 | Otherwise, why dont you start an http server, and expose some websockets.
63 | */
64 | func LockThread() {
65 | for {
66 | time.Sleep(1 * time.Second)
67 | runtime.Gosched()
68 | }
69 | }
70 |
71 | /*
72 | Initialize and Enable the internal *log.Logger.
73 | */
74 | func InitLogger() {
75 | common.InitLogger("")
76 | }
77 |
78 | /*
79 | Disable the internal *log.Logger instance
80 | */
81 | func DisableLogger() {
82 | common.InitLogger("none")
83 | }
84 |
85 | /*
86 | ALWAYS use this method instead of os.Exit()
87 | This method will handle destroying the child process, and exiting as cleanly as possible.
88 | */
89 | func Exit() {
90 | connection.CleanExit()
91 | }
92 |
93 | /*
94 | Sets the Application Name
95 | */
96 | func SetApplicationName(name string) {
97 | spawn.ApplicationName = name
98 | }
99 |
100 | /*
101 | Create a new EventHandler for a give event.
102 | */
103 | func NewEventHandler(event string, fn interface{}) (events.ThrustEventHandler, error) {
104 | return events.NewHandler(event, fn)
105 | }
106 |
--------------------------------------------------------------------------------
/lib/spawn/spawn_process.go:
--------------------------------------------------------------------------------
1 | package spawn
2 |
3 | /*
4 | Package spawn implements methods and interfaces used in downloading and spawning the underlying thrust core binary.
5 | */
6 | import (
7 | "fmt"
8 | "os"
9 | "os/exec"
10 | "os/user"
11 | "path/filepath"
12 | "runtime"
13 |
14 | . "github.com/miketheprogrammer/go-thrust/lib/common"
15 | "github.com/miketheprogrammer/go-thrust/lib/connection"
16 | )
17 |
18 | const (
19 | thrustVersion = "0.7.6"
20 | )
21 |
22 | var (
23 | // ApplicationName only functionally applies to OSX builds, otherwise it is only cosmetic
24 | ApplicationName = "Go Thrust"
25 | // base directory for storing the executable
26 | base = ""
27 | )
28 |
29 | /*
30 | SetBaseDirectory sets the base directory used in the other helper methods
31 | */
32 | func SetBaseDirectory(dir string) error {
33 | if len(dir) == 0 {
34 | usr, err := user.Current()
35 | if err != nil {
36 | fmt.Println(err)
37 | }
38 | dir = usr.HomeDir
39 | }
40 | dir, err := filepath.Abs(dir)
41 | if err != nil {
42 | fmt.Println("Could not calculate absolute path", err)
43 | return err
44 | }
45 | base = dir
46 |
47 | return nil
48 | }
49 |
50 | /*
51 | The SpawnThrustCore method is a bootstrap and run method.
52 | It will try to detect an installation of thrust, if it cannot find it
53 | it will download the version of Thrust detailed in the "common" package.
54 | Once downloaded, it will launch a process.
55 | Go-Thrust and all *-Thrust packages communicate with Thrust Core via Stdin/Stdout.
56 | using -log=debug as a command switch will give you the most information about what is going on. -log=info will give you notices that stuff is happening.
57 | Any log level higher than that will output nothing.
58 | */
59 | func Run() {
60 | if Log == nil {
61 | InitLogger("debug")
62 | }
63 | if base == "" {
64 | SetBaseDirectory("") // Default to usr.homedir.
65 | }
66 |
67 | thrustExecPath := GetExecutablePath()
68 | if len(thrustExecPath) > 0 {
69 |
70 | if provisioner == nil {
71 | SetProvisioner(NewThrustProvisioner())
72 | }
73 | if err := provisioner.Provision(); err != nil {
74 | panic(err)
75 | }
76 |
77 | thrustExecPath = GetExecutablePath()
78 |
79 | Log.Print("Attempting to start Thrust Core")
80 | Log.Print("CMD:", thrustExecPath)
81 | cmd := exec.Command(thrustExecPath)
82 | cmdIn, e1 := cmd.StdinPipe()
83 | cmdOut, e2 := cmd.StdoutPipe()
84 |
85 | if e1 != nil {
86 | fmt.Println(e1)
87 | os.Exit(2) // need to improve exit codes
88 | }
89 |
90 | if e2 != nil {
91 | fmt.Println(e2)
92 | os.Exit(2)
93 | }
94 |
95 | if LogLevel != "none" {
96 | cmd.Stderr = os.Stdout
97 | }
98 |
99 | if err := cmd.Start(); err != nil {
100 | Log.Panic("Thrust Core not started.")
101 | }
102 |
103 | Log.Print("Thrust Core started.")
104 |
105 | // Setup our Connection.
106 | connection.Stdout = cmdOut
107 | connection.Stdin = cmdIn
108 | connection.ExecCommand = cmd
109 | connection.InitializeThreads()
110 | return
111 | } else {
112 | fmt.Println("===============WARNING================")
113 | fmt.Println("Current operating system not supported", runtime.GOOS)
114 | fmt.Println("===============END====================")
115 | }
116 | return
117 | }
118 |
--------------------------------------------------------------------------------
/tutorials/advanced_session/advanced_session.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/miketheprogrammer/go-thrust/lib/bindings/session"
5 | "github.com/miketheprogrammer/go-thrust/lib/commands"
6 | "github.com/miketheprogrammer/go-thrust/lib/common"
7 | "github.com/miketheprogrammer/go-thrust/thrust"
8 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
9 | )
10 |
11 | func main() {
12 | /*
13 | use basic setup
14 | */
15 | thrust.InitLogger()
16 | // Set any Custom Provisioners before Start
17 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
18 | // thrust.Start() must always come before any bindings are created.
19 | thrust.Start()
20 |
21 | /*
22 | Start of Advanced Session Tutorial.
23 | We are going to set the Override value to true as ooposed to the false
24 | used in basic_session. This will cause ThrustCore to try to invoke methods from us.
25 |
26 | Look down below func main to find our session class.
27 | */
28 | mysession := thrust.NewSession(false, true, "session_cache")
29 |
30 | mysession.SetInvokable(NewSimpleSession())
31 | /*
32 | Modified basic_window, where we provide, a session argument
33 | to NewWindow.
34 | */
35 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
36 | RootUrl: "http://breach.cc/",
37 | Session: mysession,
38 | })
39 | thrustWindow.Show()
40 | thrustWindow.Maximize()
41 | thrustWindow.Focus()
42 |
43 | // In lieu of something like an http server, we need to lock this thread
44 | // in order to keep it open, and keep the process running.
45 | // Dont worry we use runtime.Gosched :)
46 | thrust.LockThread()
47 | }
48 |
49 | // SimpleSession must subscribe to the contract set forth by SessionInvokable interface
50 |
51 | type SimpleSession struct {
52 | cookieStore map[string][]session.Cookie
53 | }
54 |
55 | func NewSimpleSession() (ss SimpleSession) {
56 | return ss
57 | }
58 |
59 | /*
60 | For Simplicity type declarations
61 | */
62 | func (ss SimpleSession) InvokeCookiesLoad(args *commands.CommandResponseArguments, s *session.Session) (cookies []session.Cookie) {
63 | common.Log.Print("InvokeCookiesLoad")
64 | cookies = make([]session.Cookie, 0)
65 |
66 | return cookies
67 | }
68 |
69 | func (ss SimpleSession) InvokeCookiesLoadForKey(args *commands.CommandResponseArguments, s *session.Session) (cookies []session.Cookie) {
70 | common.Log.Print("InvokeCookiesLoadForKey")
71 | cookies = make([]session.Cookie, 0)
72 |
73 | return cookies
74 | }
75 |
76 | func (ss SimpleSession) InvokeCookiesFlush(args *commands.CommandResponseArguments, s *session.Session) bool {
77 | common.Log.Print("InvokeCookiesFlush")
78 | return false
79 | }
80 |
81 | func (ss SimpleSession) InvokeCookiesAdd(args *commands.CommandResponseArguments, s *session.Session) bool {
82 | common.Log.Print("InvokeCookiesAdd")
83 | return false
84 | }
85 |
86 | func (ss SimpleSession) InvokeCookiesUpdateAccessTime(args *commands.CommandResponseArguments, s *session.Session) bool {
87 | common.Log.Print("InvokeCookiesUpdateAccessTime")
88 | return false
89 | }
90 |
91 | func (ss SimpleSession) InvokeCookiesDelete(args *commands.CommandResponseArguments, s *session.Session) bool {
92 | common.Log.Print("InvokeCookiesDelete")
93 | return false
94 | }
95 |
96 | func (ss SimpleSession) InvokeCookieForceKeepSessionState(args *commands.CommandResponseArguments, s *session.Session) {
97 | common.Log.Print("InvokeCookieForceKeepSessionState")
98 | }
99 |
--------------------------------------------------------------------------------
/tutorials/basic_window_events/basic_window_events.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/miketheprogrammer/go-thrust/lib/commands"
9 | "github.com/miketheprogrammer/go-thrust/lib/connection"
10 | "github.com/miketheprogrammer/go-thrust/thrust"
11 | "github.com/miketheprogrammer/go-thrust/tutorials/provisioner"
12 | )
13 |
14 | /*
15 | This tutorial teaches how to handle global events.
16 | You can even use this to track menu/window/session etc. events,
17 | if you store your bindings somewhere and track the ids.
18 | Check package thrust for the acceptable handler definitions.
19 | */
20 | func main() {
21 | thrust.InitLogger()
22 | // Set any Custom Provisioners before Start
23 | thrust.SetProvisioner(tutorial.NewTutorialProvisioner())
24 | // thrust.Start() must always come before any bindings are created.
25 | thrust.Start()
26 |
27 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
28 | RootUrl: "http://breach.cc/",
29 | })
30 | thrustWindow.Show()
31 |
32 | /*
33 | Here we use an EventResult Callback, it provides us a little less data than a ComandResponse cb
34 | however, its good for if we know we dont need to worry about targetids. Like understanding which window
35 | was focuses, just that there is a window, that was focused.
36 | */
37 | onfocus, err := thrust.NewEventHandler("focus", func(er commands.EventResult) {
38 | fmt.Println("Focus Event Occured")
39 | })
40 | fmt.Println(onfocus)
41 | if err != nil {
42 | fmt.Println(err)
43 | connection.CleanExit()
44 | }
45 |
46 | /*
47 | Note blur does not seem to be triggered by unfocus
48 | only by actually clicking the window, and then clicking somewhere else.
49 | */
50 | onblur, err := thrust.NewEventHandler("blur", func(er commands.EventResult) {
51 | fmt.Println("Blur Event Occured")
52 | })
53 | fmt.Println(onblur)
54 | if err != nil {
55 | fmt.Println(err)
56 | connection.CleanExit()
57 | }
58 |
59 | /*
60 | Here we use a CommandResponse callback just because we can, it provides us more data
61 | than the EventResult callback
62 | */
63 | onclose, err := thrust.NewEventHandler("closed", func(cr commands.EventResult) {
64 | fmt.Println("Close Event Occured")
65 | })
66 | fmt.Println(onclose)
67 | if err != nil {
68 | fmt.Println(err)
69 | connection.CleanExit()
70 | }
71 |
72 | /*
73 | Lets say we just want to log all events
74 | */
75 | onanything, err := thrust.NewEventHandler("*", func(cr commands.CommandResponse) {
76 | cr_marshaled, err := json.Marshal(cr)
77 | if err != nil {
78 | fmt.Println(err)
79 | } else {
80 | fmt.Println(fmt.Sprintf("Event(%s) - Signaled by Command (%s)", cr.Type, cr_marshaled))
81 | }
82 | })
83 |
84 | fmt.Println(onanything)
85 | if err != nil {
86 | fmt.Println(err)
87 | connection.CleanExit()
88 | }
89 |
90 | time.AfterFunc(time.Second, func() {
91 | thrustWindow.Focus()
92 | })
93 |
94 | time.AfterFunc(time.Second*2, func() {
95 | thrustWindow.UnFocus()
96 | })
97 |
98 | time.AfterFunc(time.Second*3, func() {
99 | thrustWindow.Close()
100 | })
101 |
102 | // Lets do a window timeout
103 | go func() {
104 | <-time.After(time.Second * 5)
105 | connection.CleanExit()
106 | }()
107 | // BLOCKING - Dont run before youve excuted all commands you want first
108 | thrust.LockThread()
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/examples/chat/server/chat.html.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | var chat_html = []byte{
4 | 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x68, 0x74,
5 | 0x6d, 0x6c, 0x3e, 0x0a, 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0a, 0x20,
6 | 0x20, 0x3c, 0x68, 0x65, 0x61, 0x64, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20,
7 | 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x7b, 0x7b, 0x74, 0x65,
8 | 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x22, 0x63, 0x68, 0x61, 0x74,
9 | 0x2e, 0x6a, 0x73, 0x22, 0x7d, 0x7d, 0x3c, 0x2f, 0x73, 0x63, 0x72, 0x69,
10 | 0x70, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x73, 0x74, 0x79,
11 | 0x6c, 0x65, 0x3e, 0x7b, 0x7b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
12 | 0x65, 0x20, 0x22, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x63, 0x73, 0x73, 0x22,
13 | 0x7d, 0x7d, 0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3e, 0x0a, 0x20,
14 | 0x20, 0x3c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x3e, 0x0a, 0x20, 0x20, 0x3c,
15 | 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, 0x6e, 0x4c, 0x6f, 0x61, 0x64, 0x3d,
16 | 0x22, 0x69, 0x6e, 0x69, 0x74, 0x28, 0x29, 0x3b, 0x22, 0x20, 0x68, 0x65,
17 | 0x69, 0x67, 0x68, 0x74, 0x3d, 0x22, 0x31, 0x30, 0x30, 0x25, 0x22, 0x3e,
18 | 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6d, 0x67, 0x20, 0x68, 0x65, 0x69, 0x67,
19 | 0x68, 0x74, 0x3d, 0x22, 0x36, 0x30, 0x70, 0x78, 0x22, 0x20, 0x77, 0x69,
20 | 0x64, 0x74, 0x68, 0x3d, 0x22, 0x32, 0x30, 0x30, 0x70, 0x78, 0x22, 0x20,
21 | 0x73, 0x72, 0x63, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
22 | 0x69, 0x2e, 0x69, 0x6d, 0x67, 0x75, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
23 | 0x44, 0x77, 0x46, 0x4b, 0x49, 0x30, 0x4a, 0x2e, 0x70, 0x6e, 0x67, 0x22,
24 | 0x2f, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x62, 0x72, 0x2f, 0x3e, 0x0a, 0x20,
25 | 0x20, 0x3c, 0x68, 0x33, 0x3e, 0x57, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65,
26 | 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x6f, 0x2d, 0x54,
27 | 0x68, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x68, 0x61, 0x74, 0x20, 0x44,
28 | 0x65, 0x6d, 0x6f, 0x2e, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0x0a, 0x20, 0x20,
29 | 0x20, 0x20, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x6d,
30 | 0x73, 0x67, 0x22, 0x3e, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0a, 0x20,
31 | 0x20, 0x20, 0x20, 0x3c, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6e, 0x61, 0x6d,
32 | 0x65, 0x3d, 0x22, 0x6d, 0x73, 0x67, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x20,
33 | 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x23, 0x22, 0x20, 0x6f,
34 | 0x6e, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x3d, 0x22, 0x72, 0x65, 0x74,
35 | 0x75, 0x72, 0x6e, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x28, 0x29, 0x3b, 0x22,
36 | 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70,
37 | 0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78,
38 | 0x74, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x6e, 0x61, 0x6d,
39 | 0x65, 0x22, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x22, 0x30, 0x22, 0x20,
40 | 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22, 0x4a, 0x2e, 0x20, 0x44, 0x6f,
41 | 0x65, 0x22, 0x20, 0x2f, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
42 | 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d,
43 | 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d,
44 | 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x20, 0x73, 0x69,
45 | 0x7a, 0x65, 0x3d, 0x22, 0x30, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65,
46 | 0x3d, 0x22, 0x22, 0x20, 0x2f, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
47 | 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65,
48 | 0x3d, 0x22, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x22, 0x20, 0x76, 0x61,
49 | 0x6c, 0x75, 0x65, 0x3d, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x22, 0x20, 0x2f,
50 | 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x66, 0x6f, 0x72, 0x6d,
51 | 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x0a,
52 | 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0a,
53 | }
54 |
--------------------------------------------------------------------------------
/lib/connection/connection.go:
--------------------------------------------------------------------------------
1 | package connection
2 |
3 | import (
4 | "bufio"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "os"
9 | "os/exec"
10 | "os/signal"
11 | "strings"
12 | "syscall"
13 |
14 | "github.com/miketheprogrammer/go-thrust/lib/commands"
15 | . "github.com/miketheprogrammer/go-thrust/lib/common"
16 | )
17 |
18 | const (
19 | SOCKET_BOUNDARY = "--(Foo)++__THRUST_SHELL_BOUNDARY__++(Bar)--"
20 | )
21 |
22 | // Single Connection
23 | //var conn net.Conn
24 | var Stdin io.WriteCloser
25 | var Stdout io.ReadCloser
26 | var ExecCommand *exec.Cmd
27 |
28 | type In struct {
29 | Commands chan *commands.Command
30 | CommandResponses chan *commands.CommandResponse
31 | Quit chan int
32 | }
33 | type Out struct {
34 | CommandResponses chan commands.CommandResponse
35 | Errors chan error
36 | }
37 |
38 | var in In
39 | var out Out
40 |
41 | /*
42 | Initializes threads with Channel Structs
43 | Opens Connection
44 | */
45 | func InitializeThreads() {
46 | //c, err := net.Dial(proto, address)
47 | //conn = c
48 |
49 | in = In{
50 | Commands: make(chan *commands.Command),
51 | CommandResponses: make(chan *commands.CommandResponse),
52 | Quit: make(chan int),
53 | }
54 |
55 | out = Out{
56 | CommandResponses: make(chan commands.CommandResponse),
57 | Errors: make(chan error),
58 | }
59 |
60 | go Reader(&out, &in)
61 | go Writer(&out, &in)
62 |
63 | go func() {
64 | fmt.Println("Registering signals")
65 | p := []os.Signal{syscall.SIGINT}
66 | c := make(chan os.Signal, len(p))
67 | signal.Notify(c, p...)
68 |
69 | for s := range c {
70 | fmt.Println("Getting signal ", s)
71 | if s == os.Interrupt || s == os.Kill {
72 | fmt.Println("Finishing clean up quiting")
73 | CleanExit()
74 | return
75 | }
76 | }
77 | }()
78 | return
79 | }
80 |
81 | func GetOutputChannels() *Out {
82 | return &out
83 | }
84 |
85 | func GetInputChannels() *In {
86 | return &in
87 | }
88 |
89 | func GetCommunicationChannels() (*Out, *In) {
90 | return GetOutputChannels(), GetInputChannels()
91 | }
92 |
93 | func Clean() {
94 | Log.Print("Killing Thrust Core")
95 | if err := ExecCommand.Process.Kill(); err != nil {
96 | Log.Print("failed to kill: ", err)
97 | }
98 | }
99 |
100 | func CleanExit() {
101 | Clean()
102 | os.Exit(0)
103 | }
104 |
105 | func Reader(out *Out, in *In) {
106 |
107 | reader := bufio.NewReader(Stdout)
108 | defer Stdin.Close()
109 | for {
110 | line, err := reader.ReadString(byte('\n'))
111 | if err != nil {
112 | fmt.Println(err)
113 | // For now lets just force cleanup and exit
114 | CleanExit()
115 | return
116 | }
117 |
118 | //Log.Print("SOCKET::Line", line)
119 | if !strings.Contains(line, SOCKET_BOUNDARY) {
120 | response := commands.CommandResponse{}
121 | json.Unmarshal([]byte(line), &response)
122 | out.CommandResponses <- response
123 | }
124 | }
125 | }
126 |
127 | func Writer(out *Out, in *In) {
128 | for {
129 | select {
130 | case response := <-in.CommandResponses:
131 | cmd, _ := json.Marshal(response)
132 | //Log.Print("Writing RESPONSE", string(cmd), "\n", SOCKET_BOUNDARY)
133 |
134 | Stdin.Write(cmd)
135 | Stdin.Write([]byte("\n"))
136 | Stdin.Write([]byte(SOCKET_BOUNDARY))
137 | Stdin.Write([]byte("\n"))
138 | case command := <-in.Commands:
139 | ActionId += 1
140 | command.ID = ActionId
141 |
142 | //fmt.Println(command)
143 | cmd, _ := json.Marshal(command)
144 | //Log.Print("Writing", string(cmd), "\n", SOCKET_BOUNDARY)
145 |
146 | Stdin.Write(cmd)
147 | Stdin.Write([]byte("\n"))
148 | Stdin.Write([]byte(SOCKET_BOUNDARY))
149 | Stdin.Write([]byte("\n"))
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/examples/chat/server/chat.js:
--------------------------------------------------------------------------------
1 | var backoff_attempts = 0;
2 | function backoff_ok() {
3 | backoff_attempts = 0;
4 | }
5 | function backoff_compute() {
6 | var SECOND = 1000;
7 | var backoff_initial = 0.5 * SECOND;
8 | var backoff_max = 60 * SECOND;
9 | var backoff_multiplier = 1.5;
10 | var backoff_randomization = 0.5;
11 |
12 | var retry = backoff_initial * Math.pow(backoff_multiplier, backoff_attempts);
13 | var limited = Math.min(retry, backoff_max);
14 |
15 | // randomness is backoff_randomization wide centered around 1.0
16 | var rand = Math.random() * backoff_randomization;
17 | rand = rand + 1 - backoff_randomization/2;
18 | var delay = limited * rand;
19 | console.log({retry: retry, limited: limited, rand: rand, delay: delay});
20 |
21 | backoff_attempts += 1;
22 | var pretty = delay/1000;
23 | console.log("Retrying websocket in " + pretty.toFixed(2) + "s...");
24 | return delay;
25 | }
26 |
27 | var ws;
28 | function connect() {
29 | var div = document.getElementById("msg");
30 | var line = document.createElement("p");
31 | line.appendChild(document.createTextNode("Connecting..."));
32 | div.appendChild(line);
33 |
34 | if (ws != null) {
35 | ws.close();
36 | ws = null;
37 | }
38 | var scheme = "ws";
39 | if (window.location.protocol == "https") {
40 | scheme = "wss";
41 | }
42 | ws = new WebSocket(scheme + "://" + window.location.host + "/sock");
43 | // Initializing these event handlers is not racy as long as we do
44 | // it before we let the execution here return to the event loop.
45 | // https://www.w3.org/Bugs/Public/show_bug.cgi?id=12510
46 | ws.onopen = function () {
47 | backoff_ok();
48 | var line = document.createElement("p");
49 | line.appendChild(document.createTextNode("Opened."));
50 | div.appendChild(line);
51 | };
52 | ws.onmessage = function (e) {
53 | // TODO can throw SyntaxError
54 | var rpc = JSON.parse(e.data);
55 | console.log("rpc in:", rpc);
56 |
57 | if (rpc.fn === undefined
58 | || rpc.fn == "") {
59 | // responses; we're currently ignoring errors
60 | return;
61 | }
62 |
63 | // kludge dispatch for now
64 | if (rpc.fn != "Chat.Message") {
65 | rpc.error = {rpc: "No such function."};
66 | delete rpc.fn;
67 | delete rpc.args;
68 | delete rpc.result;
69 | ws.send(rpc)
70 | return;
71 | }
72 |
73 | // TODO catch exceptions
74 |
75 | var line = document.createElement("p");
76 |
77 | var time = document.createElement("span");
78 | time.setAttribute("class", "time");
79 | time.appendChild(document.createTextNode(rpc.args.time));
80 | line.appendChild(time);
81 |
82 | var from = document.createElement("span");
83 | from.setAttribute("class", "from");
84 | from.appendChild(document.createTextNode(rpc.args.from));
85 | line.appendChild(from);
86 |
87 | var text = document.createElement("span");
88 | text.setAttribute("class", "message");
89 | text.appendChild(document.createTextNode(rpc.args.message));
90 | line.appendChild(text);
91 |
92 | div.appendChild(line);
93 |
94 | delete rpc.fn;
95 | delete rpc.args;
96 | rpc.result = {};
97 | delete rpc.error;
98 | console.log("rpc out:", rpc);
99 | ws.send(JSON.stringify(rpc));
100 | };
101 | ws.onclose = function (e) {
102 | var line = document.createElement("p");
103 | line.appendChild(document.createTextNode("Closed."));
104 | div.appendChild(line);
105 |
106 | // If this websocket is still the main one, reconnect;
107 | // otherwise, let it die. This allows easy fiddling from js
108 | // console with just ``connect()``. Ordering of the status
109 | // messages gets weird, though.
110 | if (this === ws) {
111 | var delay = backoff_compute();
112 | setTimeout(connect, delay);
113 | }
114 | };
115 | }
116 | function init() {
117 | document.msgform.message.focus();
118 | connect();
119 | };
120 |
121 | function send() {
122 | var rpc = {
123 | // TODO
124 | id: "0",
125 | fn: "Chat.Message",
126 | args: {
127 | from: document.msgform.name.value,
128 | message: document.msgform.message.value
129 | }
130 | };
131 | console.log("rpc out:", rpc);
132 | ws.send(JSON.stringify(rpc));
133 | document.msgform.message.value = "";
134 | return false;
135 | };
136 |
--------------------------------------------------------------------------------
/lib/commands/commands.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import "encoding/json"
4 |
5 | /*
6 | commands package contains structures for working with JSON RPC
7 | Calls to ThrustCore
8 | */
9 |
10 | /*
11 | Command defines the structure used in send json rpc messages to ThrustCore
12 | */
13 | type Command struct {
14 | ID uint `json:"_id"`
15 | Action string `json:"_action"`
16 | ObjectType string `json:"_type,omitempty"`
17 | Method string `json:"_method"`
18 | TargetID uint `json:"_target,omitempty"`
19 | Args CommandArguments `json:"_args"`
20 | }
21 |
22 | /*
23 | CommandArguments defines the structure used in providing arguments
24 | to Command's when talking to ThrustCore
25 | Covers all possible argument combinations.
26 | Makes use of omit empty to adapt to different use cases
27 | */
28 | type CommandArguments struct {
29 | RootUrl string `json:"root_url,omitempty"`
30 | Title string `json:"title,omitempty"`
31 | Size SizeHW `json:"size,omitempty"`
32 | Position PositionXY `json:"position,omitempty"`
33 | X int `json:"x,omitempty"`
34 | Y int `json:"y,omitempty"`
35 | Width uint `json:"width,omitempty"`
36 | Height uint `json:"height,omitempty"`
37 | CommandID uint `json:"command_id,omitempty"`
38 | Label string `json:"label,omitempty"`
39 | MenuID uint `json:"menu_id,omitempty"` // this should never be 0 anyway
40 | WindowID uint `json:"window_id,omitempty"`
41 | SessionID uint `json:"session_id,omitempty"`
42 | GroupID uint `json:"group_id,omitempty"`
43 | Value bool `json:"value"`
44 | CookieStore bool `json:"cookie_store"`
45 | OffTheRecord bool `json:"off_the_record"`
46 | Fullscreen bool `json:"fullscreen"`
47 | Kiosk bool `json:"kiosk"`
48 | Focus bool `json:"focus"`
49 | HasFrame bool `json:"has_frame"`
50 | IconPath string `json:"icon_path,omitempty"`
51 | Path string `json:"path,omitempty"`
52 | Message RemoteMessage `json:"message,omitempty"`
53 | }
54 |
55 | /*
56 | SizeHW is a simple struct defining Height and Width
57 | */
58 | type SizeHW struct {
59 | Width uint `json:"width,omitempty"`
60 | Height uint `json:"height,omitempty"`
61 | }
62 |
63 | type PositionXY struct {
64 | X int `json:"x,omitempty"`
65 | Y int `json:"y,omitempty"`
66 | }
67 |
68 | /*
69 | ReplyResult is used in CommandResponse's of Type Reply
70 | */
71 | type ReplyResult struct {
72 | TargetID uint `json:"_target,omitempty"`
73 | Cookies json.RawMessage `json:"cookies"`
74 | }
75 |
76 | /*
77 | EventResult is used in CommandResponse's of Type Event
78 | */
79 | type EventResult struct {
80 | CommandID uint `json:"command_id,omitempty"`
81 | EventFlags int `json:"event_flags,omitempty"`
82 | Message RemoteMessage `json:"message,omitempty"`
83 | Type string `json:"-"`
84 | }
85 |
86 | /*
87 | Remote Message
88 | */
89 | type RemoteMessage struct {
90 | Payload string `json:"payload"`
91 | }
92 |
93 | /*
94 | CommandResponse defines the structure of a response
95 | from a Command sent to ThrustCore
96 | */
97 | type CommandResponse struct {
98 | Action string `json:"_action,omitempty"`
99 | Error string `json:"_error,omitempty"`
100 | ID uint `json:"_id,omitempty"`
101 | Result ReplyResult `json:"_result,omitempty"`
102 | Event EventResult `json:"_event,omitempty"`
103 | Args CommandResponseArguments `json:"_args,omitempty"`
104 | TargetID uint `json:"_target,omitempty"`
105 | Method string `json:"_method,omitempty"`
106 | Type string `json:"_type,omitempty"`
107 | }
108 |
109 | /*
110 | Command Response Arguments covers all possible cases of CommandResponses that contain an _args parameter.
111 | This is often the case with methods on the Session Object that are Invoked from the core.
112 | */
113 | type CommandResponseArguments struct {
114 | key string `json:"key,omitempty"`
115 | }
116 |
--------------------------------------------------------------------------------
/examples/chat/server/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "html/template"
7 | "log"
8 | "net"
9 | "net/http"
10 | "os"
11 | "path"
12 | "time"
13 |
14 | "github.com/gorilla/websocket"
15 | "github.com/miketheprogrammer/go-thrust/thrust"
16 | "github.com/tv42/birpc"
17 | "github.com/tv42/birpc/wetsock"
18 | "github.com/tv42/topic"
19 | )
20 |
21 | var (
22 | host = flag.String("host", "0.0.0.0", "IP address to bind to")
23 | port = flag.Int("port", 8000, "TCP port to listen on")
24 | )
25 |
26 | var html *template.Template = template.New("main")
27 |
28 | func init() {
29 | template.Must(html.New("chat.html").Parse(string(chat_html)))
30 | template.Must(html.New("chat.css").Parse(string(chat_css)))
31 | template.Must(html.New("chat.js").Parse(string(chat_js)))
32 | }
33 |
34 | func Usage() {
35 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
36 | flag.PrintDefaults()
37 | }
38 |
39 | type Incoming struct {
40 | From string
41 | Message string
42 | }
43 |
44 | type Outgoing struct {
45 | Time time.Time `json:"time"`
46 | From string `json:"from"`
47 | Message string `json:"message"`
48 | }
49 |
50 | func index(w http.ResponseWriter, req *http.Request) {
51 | w.Header().Set("Content-Type", "text/html")
52 | w.WriteHeader(http.StatusOK)
53 | err := html.ExecuteTemplate(w, "chat.html", nil)
54 | if err != nil {
55 | log.Printf("Template error: %v", err)
56 | }
57 | }
58 |
59 | type Chat struct {
60 | broadcast *topic.Topic
61 | registry *birpc.Registry
62 | }
63 |
64 | // Closing the socket from another goroutine causes a concurrent
65 | // blocking read to return a net.errClosing error. It's pointless to
66 | // spam logs with it. The error is not exported, so checking for it is
67 | // ugly.
68 | //
69 | // Background: https://code.google.com/p/go/issues/detail?id=4373
70 | func isErrClosing(err error) bool {
71 | switch err2 := err.(type) {
72 | case *net.OpError:
73 | err = err2.Err
74 | }
75 | return err.Error() == "use of closed network connection"
76 | }
77 |
78 | type nothing struct{}
79 |
80 | func (c *Chat) Message(msg *Incoming, _ *nothing, ws *websocket.Conn) error {
81 | log.Printf("recv from %v:%#v\n", ws.RemoteAddr(), msg)
82 |
83 | c.broadcast.Broadcast <- Outgoing{
84 | Time: time.Now(),
85 | From: msg.From,
86 | Message: msg.Message,
87 | }
88 | return nil
89 | }
90 |
91 | func main() {
92 | prog := path.Base(os.Args[0])
93 | log.SetFlags(0)
94 | log.SetPrefix(prog + ": ")
95 |
96 | flag.Usage = Usage
97 | flag.Parse()
98 |
99 | if flag.NArg() > 0 {
100 | Usage()
101 | os.Exit(1)
102 | }
103 |
104 | log.Printf("Serving at http://%s:%d/", *host, *port)
105 |
106 | chat := Chat{}
107 | chat.broadcast = topic.New()
108 | chat.registry = birpc.NewRegistry()
109 | chat.registry.RegisterService(&chat)
110 | defer close(chat.broadcast.Broadcast)
111 | upgrader := websocket.Upgrader{}
112 |
113 | serve := func(w http.ResponseWriter, req *http.Request) {
114 | ws, err := upgrader.Upgrade(w, req, nil)
115 | if err != nil {
116 | log.Println(err)
117 | return
118 | }
119 | endpoint := wetsock.NewEndpoint(chat.registry, ws)
120 | messages := make(chan interface{}, 10)
121 | chat.broadcast.Register(messages)
122 | go func() {
123 | defer chat.broadcast.Unregister(messages)
124 | for i := range messages {
125 | msg := i.(Outgoing)
126 | // Fire-and-forget.
127 | // TODO use .Notify when it exists
128 | _ = endpoint.Go("Chat.Message", msg, nil, nil)
129 | }
130 | // broadcast topic kicked us out for being too slow;
131 | // probably a hung TCP connection. let client
132 | // re-establish.
133 | log.Printf("Kicking slow client: %v", ws.RemoteAddr())
134 | ws.Close()
135 | }()
136 |
137 | if err := endpoint.Serve(); err != nil {
138 | log.Printf("websocket error from %v: %v", ws.RemoteAddr(), err)
139 | }
140 | }
141 |
142 | http.HandleFunc("/sock", serve)
143 | http.Handle("/", http.HandlerFunc(index))
144 | addr := fmt.Sprintf("%s:%d", *host, *port)
145 |
146 | thrust.InitLogger()
147 | thrust.Start()
148 | thrustWindow := thrust.NewWindow(thrust.WindowOptions{
149 | RootUrl: fmt.Sprintf("http://127.0.0.1:%d", *port),
150 | })
151 | thrustWindow.Show()
152 | thrustWindow.Focus()
153 |
154 | err := http.ListenAndServe(addr, nil)
155 | if err != nil {
156 | log.Fatal(err)
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/lib/bindings/session/session.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | /*
4 | Pacage Session contains an API Binding, and
5 | interfaces that will assist in accessing the
6 | default Session or implementing your own Custom Session.
7 | */
8 | import (
9 | "encoding/json"
10 |
11 | . "github.com/miketheprogrammer/go-thrust/lib/commands"
12 | . "github.com/miketheprogrammer/go-thrust/lib/common"
13 | "github.com/miketheprogrammer/go-thrust/lib/connection"
14 | "github.com/miketheprogrammer/go-thrust/lib/dispatcher"
15 | )
16 |
17 | /*
18 | Session is the core API Binding object used to communicate with Thrust.
19 | */
20 | type Session struct {
21 | TargetID uint
22 | CookieStore bool
23 | OffTheRecord bool
24 | Path string
25 | Ready bool
26 | CommandHistory []*Command
27 | ResponseHistory []*CommandResponse
28 | WaitingResponses []*Command
29 | CommandQueue []*Command
30 | SendChannel *connection.In
31 | SessionOverrideInterface SessionInvokable
32 | }
33 |
34 | /*
35 | NewSession is a constructor that takes 3 arguments,
36 | incognito which is a boolean, meaning dont persist session state
37 | after close.
38 | overrideDefaultSession which is a boolean that till tell thrust core
39 | to try to invoke session methods from us.
40 | path string is the path to store session data.
41 | */
42 | func NewSession(incognito, overrideDefaultSession bool, path string) *Session {
43 | session := Session{
44 | CookieStore: overrideDefaultSession,
45 | OffTheRecord: incognito,
46 | Path: path,
47 | }
48 | if overrideDefaultSession == true {
49 | session.SetInvokable(*NewDummySession())
50 | }
51 | command := Command{
52 | Action: "create",
53 | ObjectType: "session",
54 | Args: CommandArguments{
55 | CookieStore: session.CookieStore,
56 | OffTheRecord: session.OffTheRecord,
57 | Path: session.Path,
58 | },
59 | }
60 | session.SendChannel = connection.GetInputChannels()
61 | session.WaitingResponses = append(session.WaitingResponses, &command)
62 | session.Send(&command)
63 | dispatcher.RegisterHandler(session.DispatchResponse)
64 | return &session
65 | }
66 |
67 | func (session *Session) HandleInvoke(reply CommandResponse) {
68 | if reply.TargetID == session.TargetID {
69 | response := &CommandResponse{
70 | Action: "reply",
71 | ID: reply.ID,
72 | Result: ReplyResult{},
73 | }
74 | switch reply.Method {
75 | case "cookies_load":
76 | cookies := session.SessionOverrideInterface.InvokeCookiesLoad(&reply.Args, session)
77 | marshaledCookies, err := json.Marshal(cookies)
78 | if err != nil {
79 | Log.Print(err)
80 | } else {
81 | response.Result.Cookies = marshaledCookies
82 | }
83 | case "cookies_load_for_key":
84 | cookies := session.SessionOverrideInterface.InvokeCookiesLoadForKey(&reply.Args, session)
85 | marshaledCookies, err := json.Marshal(cookies)
86 | if err != nil {
87 | Log.Print(err)
88 | } else {
89 | response.Result.Cookies = marshaledCookies
90 | }
91 | case "cookies_flush":
92 | session.SessionOverrideInterface.InvokeCookiesFlush(&reply.Args, session)
93 | case "cookies_add":
94 | session.SessionOverrideInterface.InvokeCookiesAdd(&reply.Args, session)
95 | case "cookies_delete":
96 | session.SessionOverrideInterface.InvokeCookiesDelete(&reply.Args, session)
97 | case "cookies_update_access_time":
98 | session.SessionOverrideInterface.InvokeCookiesUpdateAccessTime(&reply.Args, session)
99 | case "cookies_force_keep_session_state":
100 | session.SessionOverrideInterface.InvokeCookieForceKeepSessionState(&reply.Args, session)
101 | }
102 | Log.Print("Sending Response to Invoke")
103 | session.SendChannel.CommandResponses <- response
104 | }
105 | }
106 |
107 | func (session *Session) HandleReply(reply CommandResponse) {
108 | Log.Print(reply)
109 | for k, command := range session.WaitingResponses {
110 | if command.ID != reply.ID {
111 | continue
112 | }
113 | if command.ID == reply.ID {
114 | Log.Print("Window(", session.TargetID, ")::Handling Reply::", reply)
115 | if len(session.WaitingResponses) > 1 {
116 | // Remove the element at index k
117 | session.WaitingResponses = session.WaitingResponses[:k+copy(session.WaitingResponses[k:], session.WaitingResponses[k+1:])]
118 | } else {
119 | // Just initialize to empty splice literal
120 | session.WaitingResponses = []*Command{}
121 | }
122 | Log.Print("session", session.TargetID, command.Action, reply.Result.TargetID)
123 | if session.TargetID == 0 && command.Action == "create" {
124 | if reply.Result.TargetID != 0 {
125 | session.TargetID = reply.Result.TargetID
126 | Log.Print("Session:: Received TargetID(", session.TargetID, ") :: Setting Ready State")
127 | session.Ready = true
128 | }
129 | }
130 | }
131 | }
132 | }
133 |
134 | func (session *Session) DispatchResponse(response CommandResponse) {
135 | switch response.Action {
136 | case "invoke":
137 | session.HandleInvoke(response)
138 | case "reply":
139 | session.HandleReply(response)
140 |
141 | }
142 | }
143 |
144 | func (session *Session) Send(command *Command) {
145 | session.SendChannel.Commands <- command
146 | }
147 |
148 | func (session *Session) SetInvokable(si SessionInvokable) {
149 | session.SessionOverrideInterface = si
150 | }
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | go-thrust
2 | =========
3 |
4 | ALERT - Breaking Changes.
5 | Please See Examples, and Tutorial Files, however Tutorial Readmes have not yet been updated..
6 | Basic Breakdown is, alot of intializers are now wrapped in the `thrust` package.
7 | i.e. window.NewWindow is now thrust.NewWindow.
8 | Import Paths have changed, please see the lib/ folder.
9 |
10 | Cross Platform UI Kit powered by Blink/V8/Chromium Content Lib
11 |
12 | Current Thrust Version 0.7.5
13 |
14 | ```bash
15 | # Fetch go-thrust (lib and command)
16 | go get -v github.com/miketheprogrammer/go-thrust
17 |
18 | # Install Thrust
19 | go-thrust install
20 | ```
21 |
22 | 
23 | Official GoLang Thrust Client for Thrust (https://github.com/breach/thrust)
24 |
25 | Chat with us
26 | ==================
27 | irc: #breach on freenode - this is the entire breach/thrust community
28 | gitter.im : https://gitter.im/miketheprogrammer/go-thrust - focused on go-thrust.
29 | [](https://gitter.im/miketheprogrammer/go-thrust?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
30 |
31 | Contributors
32 | ===================
33 | Michael Hernandez (miketheprogrammer)
34 |
35 | Francis Bouvier
36 |
37 | tehbilly
38 |
39 | gronpipmaster
40 |
41 | ShionRyuu
42 |
43 | Toorop
44 |
45 | Quick Start, see it in action
46 | ==================
47 | On Linux/Darwin
48 |
49 | Start Go Thrust using source and GoLang runtime
50 | ```bash
51 | go run tutorials/basic_window/basic_window.go
52 | ```
53 |
54 | For Single Binary Install please use the tutorial
55 | ```bash
56 | cd tutorials/advanced_single_binary_distribution
57 | mkdir vendor
58 | cd vendor
59 | wget https://github.com/breach/thrust/releases/download/v0.7.6/thrust-v0.7.6-darwin-x64.zip
60 | cd ..
61 | go-bindata -pkg provisioner -o provisioner/vendor.go vendor/
62 | go run advanced_single_binary_distribution.go
63 |
64 | ```
65 |
66 | Forward:
67 | ==================
68 | Go-Thrust is a cross platform GUI Application development base. It aims to provide the
69 | essentials to create a cross platform application using web technologies such as, (HTML,CSS,JS),
70 | as well as new technologies like Websockets, Webview, etcetera. Go-Thrust is the supported Go Library for accessing the underlying technology Thrust. Thrust builds upon the efforts of predecessors like ExoBrowser, Node-Webkit, and Atom-Shell to create an easily buildable Webbrowser base. Thrust consists essentially of a C/C++ implementation of the Chromium Content Library (Blink/V8) and abstracts away most of the nitty gritty platform specific implementations.
71 |
72 | While Go Thrust and Thrust are in their infancy, they already provide some pretty nice features.
73 | Expect many more to come.
74 |
75 | Thrust exposes all of this beautifully over an Stdin/Stdout pipe speaking a JSONRPC protocol.
76 |
77 | DOCUMENTATION (Docs and Tutorials)
78 | ================
79 | * [Tutorials](https://github.com/miketheprogrammer/go-thrust/tree/master/tutorials)
80 | * [Examples](https://github.com/miketheprogrammer/go-thrust/tree/master/examples)
81 | * [GoDoc](http://godoc.org/github.com/miketheprogrammer/go-thrust)
82 |
83 |
84 | Current Platform Specific Cases:
85 | ================
86 | Currently Darwin handles Application Menus different from other systems.
87 | The "Root" menu is always named the project name. However on linux, the name is whatever you provide. This can be seen in demo.go, and can be alleviated by trying to use the same name, or by using different logic for both cases. I make no attempt to unify it at the library level, because the user deserves the freedom to do this themselves.
88 |
89 | This thrust client exposes enough Methods to be fairly forwards compatible even without adding new helper methods. The beauty of working with a stable JSON RPC Protocol is that most methods are just helpers around build that data structure.
90 |
91 | Helper methods receive the UpperCamelCase version of their relative names in Thrust.
92 |
93 | i.e. insert_item_at == InsertItemAt
94 |
95 | Please note that the intended use case of Application Menus is to only support
96 | OSX and Unity/X11 global menu bars. This means that you should implement most menus in html and javascript, using IPC/RPC to communicate with the host application. The side effect is primarily that Windows, and certain unix/linux systems will not load ApplicationMenus
97 |
98 |
99 | Using Go-Thrust as a Library and bundling your final release
100 | ==========================
101 | To use go-thrust as a library, simple use the code in the same way you would use any GoLang library.
102 |
103 | - More info to come on Prepping releases.
104 |
105 | Current Platform Targets
106 | ================
107 | Linux(x64), Darwin(x64), Windows(ia32). - These targets depend on Thrust Core, obviously the go library will run on any target, but the core may not.
108 |
109 | Unfortunately because Thrust is built around C and Go its not exactly portable to Android and IOS, this may be possible in the future, but not yet. However, since we are primarily building a web app, we may be able to do something at a later time. For instance, there is work being done on allowing java to run go libraries. This would allow us to hopefully covert our application menus, to maybe just an html menu at the top, and just serve the page, instead of using Thrust. Thats all for the future however, for now, enjoy CPD for Linux,Darwin,Windows.
110 |
111 |
112 | The Future of Go-Thrust
113 | ================
114 | Any user of Go-Thrust should feel free to contribute ideas, code, anything that can help move this project in the right direction. The ideal goal is to stay as much out of the way, but still provide a useful system.
115 | ```
116 |
--------------------------------------------------------------------------------
/examples/chat/server/chat.css.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | var chat_css = []byte{
4 | 0x23, 0x6d, 0x73, 0x67, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62,
5 | 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73,
6 | 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x20,
7 | 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20,
8 | 0x30, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
9 | 0x61, 0x78, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x38,
10 | 0x30, 0x25, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x6d, 0x73, 0x67, 0x20,
11 | 0x70, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67,
12 | 0x69, 0x6e, 0x3a, 0x20, 0x30, 0x2e, 0x31, 0x65, 0x6d, 0x3b, 0x0a, 0x7d,
13 | 0x0a, 0x0a, 0x23, 0x6d, 0x73, 0x67, 0x20, 0x73, 0x70, 0x61, 0x6e, 0x20,
14 | 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e,
15 | 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x65,
16 | 0x6d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x6d, 0x73, 0x67, 0x20, 0x2e,
17 | 0x74, 0x69, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
18 | 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20,
19 | 0x6d, 0x6f, 0x6e, 0x6f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3b, 0x0a, 0x20,
20 | 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65,
21 | 0x3a, 0x20, 0x38, 0x30, 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
22 | 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x34, 0x30, 0x34, 0x30, 0x34,
23 | 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
24 | 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a,
25 | 0x23, 0x6d, 0x73, 0x67, 0x20, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x7b,
26 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
27 | 0x62, 0x6c, 0x75, 0x65, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x6d, 0x73,
28 | 0x67, 0x20, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x3a, 0x61, 0x66, 0x74, 0x65,
29 | 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74,
30 | 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x22, 0x3a, 0x22, 0x3b, 0x0a, 0x7d, 0x0a,
31 | 0x0a, 0x23, 0x6d, 0x73, 0x67, 0x20, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61,
32 | 0x67, 0x65, 0x20, 0x7b, 0x0a, 0x7d, 0x0a, 0x0a, 0x66, 0x6f, 0x72, 0x6d,
33 | 0x5b, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x6d, 0x73, 0x67, 0x66, 0x6f,
34 | 0x72, 0x6d, 0x22, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
35 | 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x39, 0x35,
36 | 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c,
37 | 0x61, 0x79, 0x3a, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d,
38 | 0x66, 0x6c, 0x65, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69,
39 | 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x2d, 0x6d, 0x6f, 0x7a, 0x2d,
40 | 0x66, 0x6c, 0x65, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69,
41 | 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x2d, 0x6d, 0x73, 0x2d, 0x66,
42 | 0x6c, 0x65, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73,
43 | 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x66, 0x6c, 0x65, 0x78, 0x3b, 0x0a,
44 | 0x20, 0x20, 0x20, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d,
45 | 0x66, 0x6c, 0x65, 0x78, 0x2d, 0x66, 0x6c, 0x6f, 0x77, 0x3a, 0x20, 0x72,
46 | 0x6f, 0x77, 0x20, 0x6e, 0x6f, 0x77, 0x72, 0x61, 0x70, 0x3b, 0x0a, 0x20,
47 | 0x20, 0x20, 0x20, 0x2d, 0x6d, 0x6f, 0x7a, 0x2d, 0x66, 0x6c, 0x65, 0x78,
48 | 0x2d, 0x66, 0x6c, 0x6f, 0x77, 0x3a, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x6e,
49 | 0x6f, 0x77, 0x72, 0x61, 0x70, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d,
50 | 0x6d, 0x73, 0x2d, 0x66, 0x6c, 0x65, 0x78, 0x2d, 0x66, 0x6c, 0x6f, 0x77,
51 | 0x3a, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x6e, 0x6f, 0x77, 0x72, 0x61, 0x70,
52 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x6d, 0x73, 0x2d, 0x66, 0x6c,
53 | 0x65, 0x78, 0x2d, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
54 | 0x3a, 0x20, 0x72, 0x6f, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d,
55 | 0x6d, 0x73, 0x2d, 0x66, 0x6c, 0x65, 0x78, 0x2d, 0x77, 0x72, 0x61, 0x70,
56 | 0x3a, 0x20, 0x6e, 0x6f, 0x77, 0x72, 0x61, 0x70, 0x3b, 0x0a, 0x20, 0x20,
57 | 0x20, 0x20, 0x66, 0x6c, 0x65, 0x78, 0x2d, 0x66, 0x6c, 0x6f, 0x77, 0x3a,
58 | 0x20, 0x72, 0x6f, 0x77, 0x20, 0x6e, 0x6f, 0x77, 0x72, 0x61, 0x70, 0x3b,
59 | 0x0a, 0x7d, 0x0a, 0x0a, 0x66, 0x6f, 0x72, 0x6d, 0x5b, 0x6e, 0x61, 0x6d,
60 | 0x65, 0x3d, 0x22, 0x6d, 0x73, 0x67, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x5d,
61 | 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
62 | 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x66, 0x6c, 0x65,
63 | 0x78, 0x3a, 0x20, 0x31, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20,
64 | 0x20, 0x20, 0x20, 0x2d, 0x6d, 0x6f, 0x7a, 0x2d, 0x66, 0x6c, 0x65, 0x78,
65 | 0x3a, 0x20, 0x31, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20, 0x20,
66 | 0x20, 0x20, 0x2d, 0x6d, 0x73, 0x2d, 0x66, 0x6c, 0x65, 0x78, 0x3a, 0x20,
67 | 0x31, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
68 | 0x66, 0x6c, 0x65, 0x78, 0x3a, 0x20, 0x31, 0x20, 0x61, 0x75, 0x74, 0x6f,
69 | 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x66, 0x6f, 0x72, 0x6d, 0x5b, 0x6e, 0x61,
70 | 0x6d, 0x65, 0x3d, 0x22, 0x6d, 0x73, 0x67, 0x66, 0x6f, 0x72, 0x6d, 0x22,
71 | 0x5d, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x6e, 0x61, 0x6d, 0x65,
72 | 0x3d, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x5d, 0x20, 0x7b, 0x0a, 0x20,
73 | 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68,
74 | 0x3a, 0x20, 0x38, 0x65, 0x6d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x66, 0x6f,
75 | 0x72, 0x6d, 0x5b, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x6d, 0x73, 0x67,
76 | 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x5d, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74,
77 | 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x73, 0x75, 0x62, 0x6d, 0x69,
78 | 0x74, 0x22, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x77,
79 | 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x66, 0x6c, 0x65, 0x78, 0x3a, 0x20,
80 | 0x30, 0x20, 0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20, 0x20,
81 | 0x20, 0x20, 0x2d, 0x6d, 0x6f, 0x7a, 0x2d, 0x66, 0x6c, 0x65, 0x78, 0x3a,
82 | 0x20, 0x30, 0x20, 0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20,
83 | 0x20, 0x20, 0x20, 0x2d, 0x6d, 0x73, 0x2d, 0x66, 0x6c, 0x65, 0x78, 0x3a,
84 | 0x20, 0x30, 0x20, 0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20,
85 | 0x20, 0x20, 0x20, 0x66, 0x6c, 0x65, 0x78, 0x3a, 0x20, 0x30, 0x20, 0x30,
86 | 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x7d, 0x0a,
87 | }
88 |
--------------------------------------------------------------------------------
/examples/jankybrowser/browser.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
108 |
237 |
238 |
239 |
240 |
241 |

242 |
<
243 |
>
244 |
245 |
246 |
250 |
251 |
252 |
253 |
254 |
--------------------------------------------------------------------------------
/lib/spawn/helper_darwin.go:
--------------------------------------------------------------------------------
1 | package spawn
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | )
10 |
11 | /*
12 | GetThrustDirectory returns the Directory where the unzipped thrust contents are.
13 | Differs between builds based on OS
14 | */
15 | func GetThrustDirectory() string {
16 | return base + "/vendor/darwin/x64/v" + thrustVersion
17 | }
18 |
19 | /*
20 | GetDownloadPath gets the download or extract directory for Thrust
21 | */
22 | func GetDownloadPath() string {
23 | return strings.Replace(filepath.Join(base, "$V"), "$V", thrustVersion, 1)
24 | }
25 |
26 | /*
27 | Get the path to the .app directory (only OSX)
28 | */
29 | func getAppDirectory() string {
30 | return base + "/vendor/darwin/x64/v" + thrustVersion + "/" + ApplicationName + ".app"
31 | }
32 |
33 | /*
34 | GetExecutablePath returns the path to the Thrust Executable
35 | Differs between builds based on OS
36 | */
37 | func GetExecutablePath() string {
38 | return GetThrustDirectory() + "/" + ApplicationName + ".app/Contents/MacOS/" + ApplicationName
39 | }
40 |
41 | /*
42 | GetDownloadURL returns the interpolatable version of the Thrust download url
43 | Differs between builds based on OS
44 | */
45 | func GetDownloadURL() string {
46 | return "https://github.com/breach/thrust/releases/download/v$V/thrust-v$V-darwin-x64.zip"
47 | }
48 |
49 | /*
50 | SetThrustApplicationTitle sets the title in the Info.plist. This method only exists on Darwin.
51 | */
52 | func Bootstrap() error {
53 | if ExecutableNotExist() == true {
54 | var err error
55 | if err = prepareExecutable(); err != nil {
56 | return err
57 | }
58 | if err = prepareInfoPropertiesListTemplate(); err != nil {
59 | return err
60 | }
61 |
62 | return writeInfoPropertiesList()
63 | }
64 |
65 | return nil
66 | }
67 |
68 | /*
69 | ExecutableNotExist checks if the executable does not exist
70 | */
71 | func ExecutableNotExist() bool {
72 | _, err := os.Stat(GetExecutablePath())
73 | fmt.Println(err)
74 | return os.IsNotExist(err)
75 | }
76 |
77 | func PathNotExist(path string) bool {
78 | _, err := os.Stat(path)
79 | return os.IsNotExist(err)
80 | }
81 |
82 | /*
83 | prepareExecutable dowloads, unzips and does alot of other magic to prepare our thrust core build.
84 | */
85 | func prepareExecutable() error {
86 | path, err := downloadFromUrl(GetDownloadURL(), base+"/$V", thrustVersion)
87 | if err != nil {
88 | return err
89 | }
90 |
91 | return UnzipExecutable(path)
92 | }
93 |
94 | func UnzipExecutable(path string) error {
95 | if err := unzip(path, GetThrustDirectory()); err != nil {
96 | return err
97 | }
98 | os.Rename(GetThrustDirectory()+"/ThrustShell.app/Contents/MacOS/ThrustShell", GetThrustDirectory()+"/ThrustShell.app/Contents/MacOS/"+ApplicationName)
99 | os.Rename(GetThrustDirectory()+"/ThrustShell.app", GetThrustDirectory()+"/"+ApplicationName+".app")
100 |
101 | if err := applySymlinks(); err != nil {
102 | panic(err)
103 | }
104 |
105 | return nil
106 | }
107 |
108 | /*
109 | ApplySymLinks exists because our unzip utility does not respect deferred symlinks. It applies all the neccessary symlinks to make the thrust core exe connect to the thrust core libs.
110 | */
111 | func applySymlinks() error {
112 | fmt.Println("Removing bad symlinks")
113 | var err error
114 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current") == false {
115 | if err = os.Remove(getAppDirectory() + "/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current"); err != nil {
116 | return err
117 | }
118 | }
119 |
120 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Frameworks") == false {
121 | if err = os.Remove(getAppDirectory() + "/Contents/Frameworks/ThrustShell Framework.framework/Frameworks"); err != nil {
122 | return err
123 | }
124 | }
125 |
126 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Libraries") == false {
127 | if err = os.Remove(getAppDirectory() + "/Contents/Frameworks/ThrustShell Framework.framework/Libraries"); err != nil {
128 | return err
129 | }
130 | }
131 |
132 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Resources") == false {
133 | if err = os.Remove(getAppDirectory() + "/Contents/Frameworks/ThrustShell Framework.framework/Resources"); err != nil {
134 | return err
135 | }
136 | }
137 |
138 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/ThrustShell Framework") == false {
139 | if err = os.Remove(getAppDirectory() + "/Contents/Frameworks/ThrustShell Framework.framework/ThrustShell Framework"); err != nil {
140 | return err
141 | }
142 | }
143 |
144 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current/Libraries") == false {
145 | if err = os.Remove(getAppDirectory() + "/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current/Libraries"); err != nil {
146 | return err
147 | }
148 | }
149 |
150 | fmt.Println("Applying Symlinks")
151 |
152 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current") == true {
153 | if err = os.Symlink(
154 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/A",
155 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current"); err != nil {
156 | return err
157 | }
158 | }
159 |
160 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Frameworks") == true {
161 | if err = os.Symlink(
162 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current/Frameworks",
163 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Frameworks"); err != nil {
164 | return err
165 | }
166 | }
167 |
168 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Libraries") == true {
169 | if err = os.Symlink(
170 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current/Libraries",
171 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Libraries"); err != nil {
172 | return err
173 | }
174 | }
175 |
176 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Resources") == true {
177 | if err = os.Symlink(
178 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current/Resources",
179 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Resources"); err != nil {
180 | return err
181 | }
182 | }
183 |
184 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/ThrustShell Framework") == true {
185 | if err = os.Symlink(
186 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current/ThrustShell Framework",
187 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/ThrustShell Framework"); err != nil {
188 | return err
189 | }
190 | }
191 |
192 | if PathNotExist(getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current/Libraries") == true {
193 | if err = os.Symlink(
194 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/A/Libraries/Libraries",
195 | getAppDirectory()+"/Contents/Frameworks/ThrustShell Framework.framework/Versions/Current/Libraries"); err != nil {
196 | return err
197 | }
198 | }
199 |
200 | return nil
201 | }
202 |
203 | func prepareInfoPropertiesListTemplate() error {
204 | plistPath := getInfoPropertiesListDirectory() + "/Info.plist"
205 |
206 | // Not an OSX user, but perhaps we should build this on each invocation anyways?
207 | // Might help prevent stale issues if the plist gets out of date/sync, although
208 | // I'm not entirely sure how important that is.
209 | if _, err := os.Stat(plistPath + ".tmpl"); os.IsNotExist(err) {
210 | plist, err := ioutil.ReadFile(plistPath)
211 |
212 | if err != nil {
213 | fmt.Println(err)
214 | return err
215 | }
216 |
217 | plistTmpl := strings.Replace(string(plist), "ThrustShell", "$$", -1)
218 | plistTmpl = strings.Replace(plistTmpl, "org.breach.$$", "org.breach.ThrustShell", 1)
219 | plistTmpl = strings.Replace(plistTmpl, "$$Application", "ThrustShellApplication", 1)
220 | //func WriteFile(filename string, data []byte, perm os.FileMode) error
221 |
222 | return ioutil.WriteFile(plistPath+".tmpl", []byte(plistTmpl), 0775)
223 | }
224 |
225 | return nil
226 | }
227 |
228 | func writeInfoPropertiesList() error {
229 | plistPath := getInfoPropertiesListDirectory() + "/Info.plist"
230 | if err := prepareInfoPropertiesListTemplate(); err == nil {
231 | plistTmpl, err := ioutil.ReadFile(plistPath + ".tmpl")
232 |
233 | if err != nil {
234 | fmt.Println(err)
235 | return err
236 | }
237 |
238 | plist := strings.Replace(string(plistTmpl), "$$", ApplicationName, -1)
239 |
240 | err = ioutil.WriteFile(plistPath, []byte(plist), 0775)
241 | if err != nil {
242 | return err
243 | }
244 | } else {
245 | fmt.Println("Could not change title")
246 | return nil
247 | }
248 | return nil
249 | }
250 |
251 | func getInfoPropertiesListDirectory() string {
252 | return GetThrustDirectory() + "/" + ApplicationName + ".app/Contents"
253 | }
254 |
--------------------------------------------------------------------------------
/lib/bindings/window/window.go:
--------------------------------------------------------------------------------
1 | package window
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/url"
7 | "path/filepath"
8 | "time"
9 |
10 | "github.com/miketheprogrammer/go-thrust/lib/bindings/session"
11 | . "github.com/miketheprogrammer/go-thrust/lib/commands"
12 | . "github.com/miketheprogrammer/go-thrust/lib/common"
13 | "github.com/miketheprogrammer/go-thrust/lib/connection"
14 | "github.com/miketheprogrammer/go-thrust/lib/dispatcher"
15 | "github.com/miketheprogrammer/go-thrust/lib/events"
16 | "github.com/miketheprogrammer/go-thrust/lib/spawn"
17 | )
18 |
19 | type Window struct {
20 | TargetID uint
21 | CommandHistory []*Command
22 | ResponseHistory []*CommandResponse
23 | WaitingResponses []*Command
24 | CommandQueue []*Command
25 | Url string
26 | Title string
27 | Ready bool
28 | Displayed bool
29 | SendChannel *connection.In `json:"-"`
30 | }
31 |
32 | type Options struct {
33 | RootUrl string
34 | Size SizeHW
35 | Title string
36 | IconPath string
37 | HasFrame bool
38 | Session *session.Session
39 | }
40 |
41 | func checkUrl(s string) (string, error) {
42 | u, err := url.Parse(s)
43 | if err != nil {
44 | return s, err
45 | }
46 | if u.Scheme == "" {
47 | p, err := filepath.Abs(s)
48 | if err != nil {
49 | return s, err
50 | }
51 | u = &url.URL{
52 | Scheme: "file",
53 | Path: p,
54 | }
55 | }
56 | return u.String(), err
57 | }
58 |
59 | func NewWindow(options Options) *Window {
60 | w := Window{}
61 | w.setOptions(options)
62 | _, sendChannel := connection.GetCommunicationChannels()
63 |
64 | size := options.Size
65 | if options.Size == (SizeHW{}) {
66 | size = SizeHW{
67 | Width: 1024,
68 | Height: 768,
69 | }
70 | }
71 |
72 | windowCreate := Command{
73 | Action: "create",
74 | ObjectType: "window",
75 | Args: CommandArguments{
76 | RootUrl: w.Url,
77 | Title: options.Title, // Should be using the options.Title, not spawn.ApplicationName
78 | Size: size,
79 | HasFrame: options.HasFrame, // This was reversed, HasFrame behavior should be 'true' (has frame) and 'false' (no frame)
80 | IconPath: options.IconPath,
81 | },
82 | }
83 | dispatcher.RegisterHandler(w.DispatchResponse)
84 | if options.Session == nil {
85 | w.SetSendChannel(sendChannel)
86 | w.WaitingResponses = append(w.WaitingResponses, &windowCreate)
87 | w.Send(&windowCreate)
88 | } else {
89 | go func() {
90 | for {
91 | if options.Session.TargetID != 0 {
92 | fmt.Println("sess", options.Session.TargetID)
93 | windowCreate.Args.SessionID = options.Session.TargetID
94 | w.SetSendChannel(sendChannel)
95 | w.WaitingResponses = append(w.WaitingResponses, &windowCreate)
96 | w.Send(&windowCreate)
97 | return
98 | }
99 | time.Sleep(time.Microsecond * 10)
100 | }
101 | }()
102 | }
103 | return &w
104 | }
105 |
106 | func (w *Window) setOptions(options Options) {
107 | u, _ := checkUrl(options.RootUrl)
108 | w.Url = u
109 | if len(w.Url) == 0 {
110 | w.Url = "http://google.com"
111 | }
112 |
113 | w.Title = options.Title
114 | if len(w.Title) == 0 {
115 | w.Title = spawn.ApplicationName
116 | }
117 | }
118 |
119 | func (w *Window) SetSendChannel(sendChannel *connection.In) {
120 | w.SendChannel = sendChannel
121 | }
122 |
123 | func (w *Window) IsTarget(targetId uint) bool {
124 | return targetId == w.TargetID
125 | }
126 |
127 | func (w *Window) HandleError(reply CommandResponse) {
128 |
129 | }
130 |
131 | func (w *Window) HandleReply(reply CommandResponse) {
132 | for k, v := range w.WaitingResponses {
133 | if v.ID != reply.ID {
134 | continue
135 | }
136 | Log.Print("Window(", w.TargetID, ")::Handling Reply::", reply)
137 | if len(w.WaitingResponses) > 1 {
138 | // Remove the element at index k
139 | w.WaitingResponses = w.WaitingResponses[:k+copy(w.WaitingResponses[k:], w.WaitingResponses[k+1:])]
140 | } else {
141 | // Just initialize to empty splice literal
142 | w.WaitingResponses = []*Command{}
143 | }
144 |
145 | // If we dont already have a TargetID then we accept a create action
146 | if w.TargetID == 0 && v.Action == "create" {
147 | if reply.Result.TargetID != 0 {
148 | w.TargetID = reply.Result.TargetID
149 | Log.Print("Received TargetID", "\nSetting Ready State")
150 | w.Ready = true
151 | }
152 |
153 | for i, _ := range w.CommandQueue {
154 | w.CommandQueue[i].TargetID = w.TargetID
155 | w.Send(w.CommandQueue[i])
156 | }
157 | // Reinitialize empty command queue, and allow gc.
158 | w.CommandQueue = []*Command{}
159 |
160 | return
161 | }
162 |
163 | if v.Action == "call" && v.Method == "show" {
164 | w.Displayed = true
165 | }
166 |
167 | }
168 | }
169 |
170 | func (w *Window) DispatchResponse(reply CommandResponse) {
171 |
172 | switch reply.Action {
173 | case "reply":
174 | w.HandleReply(reply)
175 | }
176 |
177 | }
178 | func (w *Window) Send(command *Command) {
179 |
180 | w.SendChannel.Commands <- command
181 | }
182 |
183 | func (w *Window) Call(command *Command) {
184 | command.Action = "call"
185 | command.TargetID = w.TargetID
186 | if w.Ready == false {
187 | w.CommandQueue = append(w.CommandQueue, command)
188 | return
189 | }
190 | w.Send(command)
191 | }
192 |
193 | func (w *Window) CallWhenReady(command *Command) {
194 | w.WaitingResponses = append(w.WaitingResponses, command)
195 | go func() {
196 | for {
197 | if w.Ready {
198 | w.Call(command)
199 | return
200 | }
201 | time.Sleep(time.Microsecond * 100)
202 | }
203 | }()
204 | }
205 |
206 | func (w *Window) CallWhenDisplayed(command *Command) {
207 | w.WaitingResponses = append(w.WaitingResponses, command)
208 | go func() {
209 | for {
210 | if w.Displayed {
211 | w.Call(command)
212 | return
213 | }
214 | time.Sleep(time.Microsecond * 100)
215 | }
216 | }()
217 | }
218 |
219 | func (w *Window) Show() {
220 | command := Command{
221 | Method: "show",
222 | }
223 |
224 | w.CallWhenReady(&command)
225 | }
226 |
227 | func (w *Window) SetTitle(title string) {
228 | command := Command{
229 | Method: "set_title",
230 | Args: CommandArguments{
231 | Title: title,
232 | },
233 | }
234 |
235 | w.CallWhenDisplayed(&command)
236 | }
237 |
238 | func (w *Window) Maximize() {
239 | command := Command{
240 | Method: "maximize",
241 | }
242 | w.CallWhenDisplayed(&command)
243 | }
244 |
245 | func (w *Window) UnMaximize() {
246 | command := Command{
247 | Method: "unmaximize",
248 | }
249 | w.CallWhenDisplayed(&command)
250 | }
251 |
252 | func (w *Window) Minimize() {
253 | command := Command{
254 | Method: "minimize",
255 | }
256 | w.CallWhenDisplayed(&command)
257 | }
258 |
259 | func (w *Window) Restore() {
260 | command := Command{
261 | Method: "restore",
262 | }
263 | w.CallWhenDisplayed(&command)
264 | }
265 |
266 | func (w *Window) Focus() {
267 | command := Command{
268 | Method: "focus",
269 | Args: CommandArguments{
270 | Focus: true,
271 | },
272 | }
273 |
274 | w.CallWhenDisplayed(&command)
275 | }
276 |
277 | func (w *Window) UnFocus() {
278 | command := Command{
279 | Method: "show",
280 | Args: CommandArguments{
281 | Focus: false,
282 | },
283 | }
284 |
285 | w.CallWhenDisplayed(&command)
286 | }
287 |
288 | func (w *Window) Fullscreen(fullscreen bool) {
289 | command := Command{
290 | Method: "set_fullscreen",
291 | Args: CommandArguments{
292 | Fullscreen: fullscreen,
293 | },
294 | }
295 |
296 | w.CallWhenDisplayed(&command)
297 | }
298 |
299 | func (w *Window) Kiosk(kiosk bool) {
300 | command := Command{
301 | Method: "set_kiosk",
302 | Args: CommandArguments{
303 | Kiosk: kiosk,
304 | },
305 | }
306 |
307 | w.CallWhenDisplayed(&command)
308 | }
309 |
310 | func (w *Window) Close() {
311 | command := Command{
312 | Method: "close",
313 | Args: CommandArguments{},
314 | }
315 |
316 | w.CallWhenDisplayed(&command)
317 | }
318 |
319 | func (w *Window) OpenDevtools() {
320 | command := Command{
321 | Method: "open_devtools",
322 | Args: CommandArguments{},
323 | }
324 |
325 | w.CallWhenDisplayed(&command)
326 | }
327 |
328 | func (w *Window) CloseDevtools() {
329 | command := Command{
330 | Method: "close_devtools",
331 | Args: CommandArguments{},
332 | }
333 |
334 | w.CallWhenDisplayed(&command)
335 | }
336 |
337 | func (w *Window) Move(x, y int) {
338 | command := Command{
339 | Method: "move",
340 | Args: CommandArguments{
341 | X: x,
342 | Y: y,
343 | },
344 | }
345 |
346 | w.CallWhenDisplayed(&command)
347 | }
348 |
349 | func (w *Window) Resize(width, height uint) {
350 | command := Command{
351 | Method: "resize",
352 | Args: CommandArguments{
353 | Width: width,
354 | Height: height,
355 | },
356 | }
357 |
358 | w.CallWhenReady(&command)
359 | }
360 |
361 | func (w *Window) Position(x, y int) {
362 | command := Command{
363 | Method: "position",
364 | Args: CommandArguments{
365 | Position: PositionXY{
366 | X: x,
367 | Y: y,
368 | },
369 | },
370 | }
371 |
372 | w.CallWhenReady(&command)
373 | }
374 |
375 | func (w *Window) SendRemoteMessage(msg string) {
376 | command := Command{
377 | Method: "remote",
378 | Args: CommandArguments{
379 | Message: RemoteMessage{
380 | Payload: msg,
381 | },
382 | },
383 | }
384 |
385 | // We dont use call, because messages are of variable size, and we definitely,
386 | // do not want to store a reference to them. So we use .Send
387 | go func() {
388 | for {
389 | if w.Displayed {
390 | command.Action = "call"
391 | command.TargetID = w.TargetID
392 | w.Send(&command)
393 | return
394 | }
395 | time.Sleep(time.Microsecond * 100)
396 | }
397 | }()
398 | }
399 |
400 | /*
401 | CRorER means commands.CommandResponse or commands.EventResult
402 | */
403 | type WindowEventHandler func(CRorER interface{}, window *Window)
404 |
405 | /*
406 | Binding Event Handlers are a bit different than global thrust handlers.
407 | The Signature of the function you pass in is WindowEventHandler
408 | */
409 | func (w *Window) HandleEvent(event string, fn interface{}) (events.ThrustEventHandler, error) {
410 | if fn, ok := fn.(func(CommandResponse, *Window)); ok == true {
411 | return events.NewHandler(event, func(cr CommandResponse) {
412 | fn(cr, w)
413 | })
414 | }
415 | if fn, ok := fn.(func(EventResult, *Window)); ok == true {
416 | return events.NewHandler(event, func(er EventResult) {
417 | fn(er, w)
418 | })
419 | }
420 | return events.ThrustEventHandler{}, errors.New("Function Signature Invalid")
421 | }
422 |
423 | func (w *Window) HandleBlur(fn interface{}) (events.ThrustEventHandler, error) {
424 | return w.HandleEvent("blur", fn)
425 | }
426 |
427 | func (w *Window) HandleRemote(fn interface{}) (events.ThrustEventHandler, error) {
428 | return w.HandleEvent("remote", fn)
429 |
430 | }
431 |
--------------------------------------------------------------------------------
/lib/bindings/menu/menu.go:
--------------------------------------------------------------------------------
1 | package menu
2 |
3 | /*
4 | Package menu:
5 | Provides common objects and methods for working with menus in
6 | Thrust. Unfortunately, we have had to use GoRoutines in this and other packages to ensure that information is passed to GoLang in the proper order.
7 | Please be aware of this when using this package in your own library.
8 | The reason this is unfortunate is that as library writers it is best to leave GoRoutines out of your library and let the user decide when to use them. However with I/O you typically need to use GoRoutines in some way or another.
9 | */
10 | import (
11 | "time"
12 |
13 | "github.com/miketheprogrammer/go-thrust/lib/bindings/window"
14 | . "github.com/miketheprogrammer/go-thrust/lib/commands"
15 | . "github.com/miketheprogrammer/go-thrust/lib/common"
16 | "github.com/miketheprogrammer/go-thrust/lib/connection"
17 | "github.com/miketheprogrammer/go-thrust/lib/dispatcher"
18 | )
19 |
20 | /*
21 | Menu is the basic object for creating and working with Menu's
22 | Provides all the necessary attributes and methods to work with asynchronous calls to the menu API.
23 | The TargetID is assigned by ThrustCore, so on init of this object, there is no TargetID. A Goroutine is dispatched to get the targetID.
24 | */
25 | type Menu struct {
26 | TargetID uint `json:"target_id,omitempty"`
27 | WaitingResponses []*Command `json:"awaiting_responses,omitempty"`
28 | CommandQueue []*Command `json:"command_queue,omitempty"`
29 | Ready bool `json:"ready"`
30 | Displayed bool `json:"displayed"`
31 | Parent *Menu `json:"-"`
32 | Children []*Menu `json:"-"`
33 | Items []*MenuItem `json:"items,omitempty"`
34 | EventRegistry []uint `json:"events,omitempty"`
35 | SendChannel *connection.In `json:"-"`
36 | Sync MenuSync `jons:"-"`
37 | ReplyHandlers map[uint]func(reply CommandResponse, item *MenuItem) `json:"_"`
38 | }
39 |
40 | /*
41 | Create a new menu object.
42 | Dispatches a call to ThrustCore to generate the object and return the new
43 | TargetID in a reply.
44 | */
45 | func NewMenu() *Menu {
46 | menu := Menu{}
47 | menuCreate := Command{
48 | Action: "create",
49 | ObjectType: "menu",
50 | }
51 | menu.Sync = MenuSync{
52 | ReadyChan: make(chan bool),
53 | DisplayedChan: make(chan bool),
54 | ChildStableChan: make(chan uint),
55 | TreeStableChan: make(chan bool),
56 | ReadyQueue: make([]*Command, 0),
57 | DisplayedQueue: make([]*Command, 0),
58 | ChildStableQueue: make([]*ChildCommand, 0),
59 | TreeStableQueue: make([]*Command, 0),
60 | }
61 | menu.ReplyHandlers = make(map[uint]func(reply CommandResponse, item *MenuItem))
62 | menu.SetSendChannel(connection.GetInputChannels())
63 | menu.WaitingResponses = append(menu.WaitingResponses, &menuCreate)
64 | dispatcher.RegisterHandler(menu.DispatchResponse)
65 |
66 | go menu.SendThread()
67 | menu.Send(&menuCreate)
68 |
69 | return &menu
70 | }
71 |
72 | /*
73 | SetSendChannel is a helper Setter for SendChannel, in case we make it private in the future.
74 | Use this for full forwards compatibility.
75 | */
76 | func (menu *Menu) SetSendChannel(sendChannel *connection.In) {
77 | menu.SendChannel = sendChannel
78 | }
79 |
80 | /*
81 | IsTarget checks if the current menu is the menu we are looking for.
82 | */
83 | func (menu *Menu) IsTarget(targetId uint) bool {
84 | return targetId == menu.TargetID
85 | }
86 |
87 | /*
88 | HandleError is a handler for Error responses from ThrustCore
89 | This should be changed to private as soon as API stabilizes.
90 | */
91 | func (menu *Menu) HandleError(reply CommandResponse) {
92 |
93 | }
94 |
95 | /*
96 | HandleEvent is a handler for Event responses from ThrustCore
97 | This should be changed to private as soon as API stabilizes.
98 | */
99 | func (menu *Menu) HandleEvent(reply CommandResponse) {
100 | for _, item := range menu.Items {
101 | if reply.Event.CommandID == item.CommandID {
102 | Log.Print("Menu(", menu.TargetID, "):: Handling Event", item.CommandID, "::Handled With Flags", reply.Event.EventFlags, "With Type", item.Type)
103 | handler, ok := menu.ReplyHandlers[item.CommandID]
104 | if ok {
105 | handler(reply, item)
106 | } else {
107 | item.HandleEvent()
108 | }
109 | return
110 | }
111 | }
112 | }
113 |
114 | /*
115 | HandleReply is a handler for Reply responses from ThrustCore
116 | This should be changed to private as soon as API stabilizes.
117 | */
118 | func (menu *Menu) HandleReply(reply CommandResponse) {
119 |
120 | for k, v := range menu.WaitingResponses {
121 | if v.ID != reply.ID {
122 | continue
123 | }
124 | Log.Print("MENU(", menu.TargetID, ")::Handling Reply", reply)
125 | removeAt := func(k int) {
126 | if len(menu.WaitingResponses) > 1 {
127 | menu.WaitingResponses = menu.WaitingResponses[:k+copy(menu.WaitingResponses[k:], menu.WaitingResponses[k+1:])]
128 | } else {
129 | menu.WaitingResponses = []*Command{}
130 | }
131 | }
132 | defer removeAt(k)
133 |
134 | if menu.TargetID == 0 && v.Action == "create" {
135 | //Assume we have a reply to action:create
136 | if reply.Result.TargetID != 0 {
137 | menu.TargetID = reply.Result.TargetID
138 | Log.Print("Received TargetID", "\nSetting Ready State")
139 | menu.Ready = true
140 | }
141 | for i, _ := range menu.CommandQueue {
142 | menu.CommandQueue[i].TargetID = menu.TargetID
143 | menu.Send(menu.CommandQueue[i])
144 | }
145 | // Reinitialize empty command queue, and allow gc.
146 | menu.CommandQueue = []*Command{}
147 | return
148 | }
149 |
150 | if v.Action == "call" && v.Method == "set_application_menu" {
151 | Log.Print("Received reply to set_application_menu", "Setting Menu Displayed to True")
152 | menu.setDisplayed(true)
153 | }
154 |
155 | }
156 | }
157 |
158 | /*
159 | setDisplayed sets the menu Displayed attribute, this is a tracking attribute and has no effect on ThrustCore or UI Layer. Thus is is private.
160 | */
161 | func (menu *Menu) setDisplayed(displayed bool) {
162 | menu.Displayed = displayed
163 |
164 | for _, child := range menu.Items {
165 | if child.IsSubMenu() {
166 | child.SubMenu.setDisplayed(displayed)
167 | }
168 | }
169 | }
170 |
171 | /*
172 | DispatchResponse dispatches CommandResponses to the proper delegates (Error, Event, Reply)
173 | */
174 | func (menu *Menu) DispatchResponse(reply CommandResponse) {
175 | switch reply.Action {
176 | case "event":
177 | menu.HandleEvent(reply)
178 | case "reply":
179 | menu.HandleReply(reply)
180 | }
181 |
182 | // for _, child := range menu.Items {
183 | // if child.IsSubMenu() {
184 | // child.SubMenu.DispatchResponse(reply)
185 | // }
186 | // }
187 | }
188 |
189 | /*
190 | SendThread is a Thread for Sending Commands based on current state of the Menu.
191 | Some commands require other events in the system to have already taken place.
192 | This thread ensures that you can run almost any command at anytime, and have it take place in the correct order. This further insures that the underlying ThrustCore api does not crash, do to improper api knowledge.
193 | */
194 | func (menu *Menu) SendThread() {
195 | //removeItemAt for []ChildCommand
196 | CCremoveItemAt := func(a []*ChildCommand, i int) []*ChildCommand {
197 | copy(a[i:], a[i+1:])
198 | a[len(a)-1] = nil // or the zero value of T
199 | a = a[:len(a)-1]
200 | return a
201 | }
202 | //removeItemAt for []*Command
203 | CremoveItemAt := func(a []*Command, i int) []*Command {
204 | copy(a[i:], a[i+1:])
205 | a[len(a)-1] = nil // or the zero value of T
206 | a = a[:len(a)-1]
207 | return a
208 | }
209 | go func() {
210 | for {
211 | if menu.Ready {
212 | menu.Sync.ReadyChan <- true
213 | }
214 | if menu.Displayed {
215 | menu.Sync.DisplayedChan <- true
216 | }
217 | for _, child := range menu.Items {
218 | if child.IsSubMenu() {
219 | if child.SubMenu.IsStable() {
220 | menu.Sync.ChildStableChan <- child.SubMenu.TargetID
221 | }
222 | }
223 | }
224 | if menu.IsTreeStable() {
225 | menu.Sync.TreeStableChan <- true
226 | }
227 | time.Sleep(time.Microsecond * 100)
228 | }
229 | }()
230 |
231 | go func() {
232 | for {
233 | select {
234 | case ready := <-menu.Sync.ReadyChan:
235 | if len(menu.Sync.ReadyQueue) == 0 || ready == false {
236 | break
237 | }
238 | command := menu.Sync.ReadyQueue[0]
239 | menu.Sync.ReadyQueue = CremoveItemAt(menu.Sync.ReadyQueue, 0)
240 | menu.Call(command)
241 | case displayed := <-menu.Sync.DisplayedChan:
242 | if len(menu.Sync.DisplayedQueue) == 0 || displayed == false {
243 | break
244 | }
245 | command := menu.Sync.DisplayedQueue[0]
246 | menu.Sync.DisplayedQueue = CremoveItemAt(menu.Sync.DisplayedQueue, 0)
247 | menu.WaitingResponses = append(menu.WaitingResponses, command)
248 | menu.Call(command)
249 | case stableChildId := <-menu.Sync.ChildStableChan:
250 | if len(menu.Sync.ChildStableQueue) == 0 {
251 | break
252 | }
253 | for i, childCommand := range menu.Sync.ChildStableQueue {
254 | if childCommand.Child.TargetID != stableChildId {
255 | continue
256 | }
257 |
258 | childCommand.Command.Args.MenuID = childCommand.Child.TargetID
259 | menu.Sync.ChildStableQueue = CCremoveItemAt(menu.Sync.ChildStableQueue, i)
260 | menu.Call(childCommand.Command)
261 | break
262 |
263 | }
264 |
265 | case treeStable := <-menu.Sync.TreeStableChan:
266 | if len(menu.Sync.TreeStableQueue) == 0 || treeStable == false {
267 | break
268 | }
269 | command := menu.Sync.TreeStableQueue[0]
270 | command.Args.MenuID = menu.TargetID
271 | menu.WaitingResponses = append(menu.WaitingResponses, command)
272 | menu.Sync.TreeStableQueue = CremoveItemAt(menu.Sync.TreeStableQueue, 0)
273 | menu.Call(command)
274 | }
275 | }
276 | }()
277 | }
278 |
279 | /*
280 | Send emits a Command over the Command SendChannel to be delivered to ThrustCore
281 | */
282 | func (menu *Menu) Send(command *Command) {
283 | menu.SendChannel.Commands <- command
284 | }
285 |
286 | /*
287 | Call turns a Command into an action:call, there are two main types of Actions for outgoing commands, create/call. There may be more added later.
288 | */
289 | func (menu *Menu) Call(command *Command) {
290 | command.Action = "call"
291 | command.TargetID = menu.TargetID
292 |
293 | if menu.Ready == false {
294 | menu.CommandQueue = append(menu.CommandQueue, command)
295 | return
296 | }
297 | menu.Send(command)
298 | }
299 |
300 | /*
301 | CallWhenReady queues up "Calls" to go out only when the Menu State is "Ready"
302 | */
303 | func (menu *Menu) CallWhenReady(command *Command) {
304 | menu.WaitingResponses = append(menu.WaitingResponses, command)
305 | menu.Sync.ReadyQueue = append(menu.Sync.ReadyQueue, command)
306 | }
307 |
308 | /*
309 | CallWhenChildStable queues up "Calls" to go out only when the state of the Child is Stable. Stable means that the child is Ready and has no AwaitingResponses
310 | */
311 | func (menu *Menu) CallWhenChildStable(command *Command, child *Menu) {
312 | menu.WaitingResponses = append(menu.WaitingResponses, command)
313 | menu.Sync.ChildStableQueue = append(menu.Sync.ChildStableQueue, &ChildCommand{
314 | Command: command,
315 | Child: child,
316 | })
317 | }
318 |
319 | /*
320 | CallWhenTreeStable queues up "Calls" to go out only when the state of the menu is Stable. Stable means that the menu is Ready and has no AwaitingResponses
321 | */
322 | func (menu *Menu) CallWhenTreeStable(command *Command) {
323 | menu.Sync.TreeStableQueue = append(menu.Sync.TreeStableQueue, command)
324 | }
325 |
326 | /*
327 | CallWhenDisplayed queues up "Calls" to go out only when
328 | the menu is Displayed
329 | */
330 | func (menu *Menu) CallWhenDisplayed(command *Command) {
331 | menu.Sync.DisplayedQueue = append(menu.Sync.DisplayedQueue, command)
332 | }
333 |
334 | /*
335 | AddItem adds a MenuItem to both the internal representation of menu and the external representation of menu
336 | */
337 | func (menu *Menu) AddItem(commandID uint, label string) {
338 | command := Command{
339 | Method: "add_item",
340 | Args: CommandArguments{
341 | CommandID: commandID,
342 | Label: label,
343 | },
344 | }
345 | menuItem := MenuItem{
346 | CommandID: commandID,
347 | Label: label,
348 | Parent: menu,
349 | Type: "item",
350 | }
351 | menu.Items = append(menu.Items, &menuItem)
352 |
353 | menu.CallWhenReady(&command)
354 | }
355 |
356 | /*
357 | AddCheckItem adds a CheckItem to both the internal representation of menu and the external representation of menu
358 | */
359 | func (menu *Menu) AddCheckItem(commandID uint, label string) {
360 | command := Command{
361 | Method: "add_check_item",
362 | Args: CommandArguments{
363 | CommandID: commandID,
364 | Label: label,
365 | },
366 | }
367 | menuItem := MenuItem{
368 | CommandID: commandID,
369 | Label: label,
370 | Type: "check",
371 | Parent: menu,
372 | }
373 | menu.Items = append(menu.Items, &menuItem)
374 | menu.CallWhenReady(&command)
375 | }
376 |
377 | /*
378 | AddRadioItem adds a RadioItem to both the internal representation of menu and the external representation of menu
379 | */
380 | func (menu *Menu) AddRadioItem(commandID uint, label string, groupID uint) {
381 | command := Command{
382 | Method: "add_radio_item",
383 | Args: CommandArguments{
384 | CommandID: commandID,
385 | Label: label,
386 | GroupID: groupID,
387 | },
388 | }
389 | menuItem := MenuItem{
390 | CommandID: commandID,
391 | Label: label,
392 | GroupID: groupID,
393 | Parent: menu,
394 | Type: "radio",
395 | }
396 | menu.Items = append(menu.Items, &menuItem)
397 | menu.CallWhenReady(&command)
398 | }
399 |
400 | /*
401 | AddSubmenu adds a SubMenu to both the internal representation of menu and the external representation of menu
402 | */
403 | func (menu *Menu) AddSubmenu(commandID uint, label string, child *Menu) {
404 | command := Command{
405 | Method: "add_submenu",
406 | Args: CommandArguments{
407 | CommandID: commandID,
408 | Label: label,
409 | },
410 | }
411 |
412 | // Assign Bidirectional navigation elements i.e. DoublyLinkedLists
413 | child.Parent = menu
414 | menuItem := MenuItem{
415 | CommandID: commandID,
416 | Label: label,
417 | SubMenu: child,
418 | Parent: menu,
419 | }
420 | menu.Items = append(menu.Items, &menuItem)
421 |
422 | menu.CallWhenChildStable(&command, child)
423 | }
424 |
425 | /*
426 | SetChecked Checks or Unchecks a CheckItem in the UI
427 | */
428 | func (menu *Menu) SetChecked(commandID uint, checked bool) {
429 | command := Command{
430 | Method: "set_checked",
431 | Args: CommandArguments{
432 | CommandID: commandID,
433 | Value: checked,
434 | },
435 | }
436 |
437 | for _, item := range menu.Items {
438 | if item.IsCommandID(commandID) {
439 | item.Checked = checked
440 | }
441 | }
442 | menu.CallWhenDisplayed(&command)
443 | }
444 |
445 | /*
446 | ToggleRadio Checks or Unchecks a RadioItem in the UI.
447 | It is used by the default event handler to turn on the expected item,
448 | and turn of other items in the group.
449 | */
450 | func (menu *Menu) ToggleRadio(commandID, groupID uint, checked bool) {
451 | for _, item := range menu.RadioGroupAtGroupID(groupID) {
452 | command := Command{
453 | Method: "set_checked",
454 | Args: CommandArguments{
455 | CommandID: item.CommandID,
456 | Value: checked,
457 | },
458 | }
459 | if item.IsCommandID(commandID) {
460 | item.Checked = checked
461 | } else {
462 | item.Checked = false
463 | command.Args.Value = false
464 | }
465 | menu.CallWhenDisplayed(&command)
466 | }
467 |
468 | }
469 |
470 | /*
471 | SetEnabled sets whether or not a given item can receive
472 | actions via the UI.
473 | */
474 | func (menu *Menu) SetEnabled(commandID uint, enabled bool) {
475 | command := Command{
476 | Method: "set_enabled",
477 | Args: CommandArguments{
478 | CommandID: commandID,
479 | Value: enabled,
480 | },
481 | }
482 |
483 | for _, item := range menu.Items {
484 | if item.IsCommandID(commandID) {
485 | item.Enabled = enabled
486 | }
487 | }
488 | menu.CallWhenDisplayed(&command)
489 | }
490 |
491 | /*
492 | SetVisible sets a boolean visibility attribute in the UI for
493 | a menu item with the given commandID.
494 | */
495 | func (menu *Menu) SetVisible(commandID uint, visible bool) {
496 | command := Command{
497 | Method: "set_visible",
498 | Args: CommandArguments{
499 | CommandID: commandID,
500 | Value: visible,
501 | },
502 | }
503 |
504 | for _, item := range menu.Items {
505 | if item.IsCommandID(commandID) {
506 | item.Visible = visible
507 | }
508 | }
509 | menu.CallWhenDisplayed(&command)
510 | }
511 |
512 | /*
513 | AddSeperator adds a Seperator Item to both the internal representation of menu and the external representation of menu.
514 | */
515 | func (menu *Menu) AddSeparator() {
516 | command := Command{
517 | Method: "add_separator",
518 | }
519 | menuItem := MenuItem{
520 | Type: "separator",
521 | Parent: menu,
522 | }
523 | menu.Items = append(menu.Items, &menuItem)
524 | menu.CallWhenReady(&command)
525 | }
526 |
527 | /*
528 | SetApplicationMenu sets the Application Menu on system that support global application level menus such as x11, unity, darwin
529 | */
530 | func (menu *Menu) SetApplicationMenu() {
531 | command := Command{
532 | Method: "set_application_menu",
533 | Args: CommandArguments{
534 | MenuID: menu.TargetID,
535 | },
536 | }
537 |
538 | // Thread to wait for Stable Menu State
539 | menu.CallWhenTreeStable(&command)
540 | }
541 |
542 | /*
543 | Popup creates a popup menu on the given window
544 | */
545 | func (menu *Menu) Popup(w *window.Window) {
546 | go func() {
547 | for {
548 | if w.TargetID != 0 {
549 | command := Command{
550 | Method: "popup",
551 | Args: CommandArguments{
552 | WindowID: w.TargetID,
553 | },
554 | }
555 |
556 | // Thread to wait for Stable Menu State
557 | menu.CallWhenTreeStable(&command)
558 | return
559 | }
560 | time.Sleep(time.Microsecond * 10)
561 | }
562 |
563 | }()
564 | }
565 |
566 | /*
567 | IsStable returns the a boolean value indicating that the menu is Ready and has no WaitingResponses
568 | */
569 | func (menu *Menu) IsStable() bool {
570 | return menu.Ready && len(menu.WaitingResponses) == 0
571 | }
572 |
573 | /*
574 | A Menu Tree is considered stable if and only if its children nodes report that they are stable.
575 | Function is recursive, so factor that in to performance
576 | */
577 | func (menu *Menu) IsTreeStable() bool {
578 | if !menu.IsStable() {
579 | return false
580 | }
581 | for _, child := range menu.Items {
582 | if child.IsSubMenu() {
583 | if !child.SubMenu.IsTreeStable() {
584 | return false
585 | }
586 | }
587 | }
588 |
589 | return true
590 | }
591 |
592 | /*
593 | ItemAtCommandID recursively searches the Menu Tree for an item with the commandID. Returns the first found match. A proper menu should not reuse
594 | commandID's
595 | */
596 | func (menu *Menu) ItemAtCommandID(commandID uint) *MenuItem {
597 | for _, item := range menu.Items {
598 | if item.IsCommandID(commandID) {
599 | return item
600 | }
601 | if item.IsSubMenu() {
602 | result := item.SubMenu.ItemAtCommandID(commandID)
603 | if result != nil {
604 | return result
605 | }
606 | }
607 | }
608 | return nil
609 | }
610 |
611 | func (menu *Menu) RegisterEventHandlerByCommandID(commandID uint, handler func(reply CommandResponse, item *MenuItem)) {
612 | menu.ReplyHandlers[commandID] = func(reply CommandResponse, item *MenuItem) {
613 | handler(reply, item)
614 | }
615 | }
616 |
617 | /*
618 | Find all menu items that belong to group identified by groupID
619 | Not recursive, as a group should be identified at the same level.
620 | Since it is not recursive you can theoretically reuse a groupID but problems
621 | could creep up elsewhere, so please use unique groupID for radio items
622 | */
623 | func (menu *Menu) RadioGroupAtGroupID(groupID uint) []*MenuItem {
624 | group := []*MenuItem{}
625 | for _, item := range menu.Items {
626 | if item.IsGroupID(groupID) {
627 | group = append(group, item)
628 | }
629 | }
630 |
631 | return group
632 | }
633 |
634 | /*
635 | DEBUG Functions
636 | */
637 | func (menu Menu) PrintRecursiveWaitingResponses() {
638 | Log.Print("Scanning Menu(", menu.TargetID, ")")
639 | if len(menu.WaitingResponses) > 0 {
640 | for _, v := range menu.WaitingResponses {
641 | Log.Print("Waiting for", v.ID, v.Action, v.Method)
642 | }
643 | }
644 |
645 | for _, child := range menu.Items {
646 | if child.IsSubMenu() {
647 | child.SubMenu.PrintRecursiveWaitingResponses()
648 | }
649 | }
650 | }
651 |
--------------------------------------------------------------------------------
/examples/chat/server/chat.js.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | var chat_js = []byte{
4 | 0x76, 0x61, 0x72, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f,
5 | 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x20, 0x3d, 0x20, 0x30,
6 | 0x3b, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x62,
7 | 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6f, 0x6b, 0x28, 0x29, 0x20,
8 | 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66,
9 | 0x66, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x20, 0x3d,
10 | 0x20, 0x30, 0x3b, 0x0a, 0x7d, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
11 | 0x6f, 0x6e, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x63,
12 | 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20,
13 | 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x53, 0x45, 0x43, 0x4f, 0x4e,
14 | 0x44, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20,
15 | 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66,
16 | 0x66, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x3d, 0x20,
17 | 0x30, 0x2e, 0x35, 0x20, 0x2a, 0x20, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44,
18 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x61,
19 | 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, 0x78, 0x20, 0x3d, 0x20,
20 | 0x36, 0x30, 0x20, 0x2a, 0x20, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x3b,
21 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x61, 0x63,
22 | 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c,
23 | 0x69, 0x65, 0x72, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x35, 0x3b, 0x0a, 0x20,
24 | 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f,
25 | 0x66, 0x66, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x69, 0x7a, 0x61,
26 | 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x2e, 0x35, 0x3b, 0x0a,
27 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x72, 0x65, 0x74,
28 | 0x72, 0x79, 0x20, 0x3d, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66,
29 | 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x2a, 0x20, 0x4d,
30 | 0x61, 0x74, 0x68, 0x2e, 0x70, 0x6f, 0x77, 0x28, 0x62, 0x61, 0x63, 0x6b,
31 | 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69,
32 | 0x65, 0x72, 0x2c, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f,
33 | 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x29, 0x3b, 0x0a, 0x20,
34 | 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74,
35 | 0x65, 0x64, 0x20, 0x3d, 0x20, 0x4d, 0x61, 0x74, 0x68, 0x2e, 0x6d, 0x69,
36 | 0x6e, 0x28, 0x72, 0x65, 0x74, 0x72, 0x79, 0x2c, 0x20, 0x62, 0x61, 0x63,
37 | 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x61, 0x78, 0x29, 0x3b, 0x0a, 0x0a,
38 | 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x72, 0x61, 0x6e, 0x64, 0x6f,
39 | 0x6d, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x69, 0x73, 0x20, 0x62, 0x61, 0x63,
40 | 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x69,
41 | 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x64, 0x65, 0x20,
42 | 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x61, 0x72, 0x6f,
43 | 0x75, 0x6e, 0x64, 0x20, 0x31, 0x2e, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20,
44 | 0x76, 0x61, 0x72, 0x20, 0x72, 0x61, 0x6e, 0x64, 0x20, 0x3d, 0x20, 0x4d,
45 | 0x61, 0x74, 0x68, 0x2e, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x28, 0x29,
46 | 0x20, 0x2a, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x72,
47 | 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
48 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x61, 0x6e, 0x64, 0x20, 0x3d,
49 | 0x20, 0x72, 0x61, 0x6e, 0x64, 0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20,
50 | 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x72, 0x61, 0x6e, 0x64,
51 | 0x6f, 0x6d, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x32, 0x3b,
52 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x64, 0x65, 0x6c,
53 | 0x61, 0x79, 0x20, 0x3d, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64,
54 | 0x20, 0x2a, 0x20, 0x72, 0x61, 0x6e, 0x64, 0x3b, 0x0a, 0x20, 0x20, 0x20,
55 | 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67,
56 | 0x28, 0x7b, 0x72, 0x65, 0x74, 0x72, 0x79, 0x3a, 0x20, 0x72, 0x65, 0x74,
57 | 0x72, 0x79, 0x2c, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x3a,
58 | 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x72, 0x61,
59 | 0x6e, 0x64, 0x3a, 0x20, 0x72, 0x61, 0x6e, 0x64, 0x2c, 0x20, 0x64, 0x65,
60 | 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x7d, 0x29,
61 | 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x6f,
62 | 0x66, 0x66, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x20,
63 | 0x2b, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61,
64 | 0x72, 0x20, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x20, 0x3d, 0x20, 0x64,
65 | 0x65, 0x6c, 0x61, 0x79, 0x2f, 0x31, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20,
66 | 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c,
67 | 0x6f, 0x67, 0x28, 0x22, 0x52, 0x65, 0x74, 0x72, 0x79, 0x69, 0x6e, 0x67,
68 | 0x20, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x69,
69 | 0x6e, 0x20, 0x22, 0x20, 0x2b, 0x20, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79,
70 | 0x2e, 0x74, 0x6f, 0x46, 0x69, 0x78, 0x65, 0x64, 0x28, 0x32, 0x29, 0x20,
71 | 0x2b, 0x20, 0x22, 0x73, 0x2e, 0x2e, 0x2e, 0x22, 0x29, 0x3b, 0x0a, 0x20,
72 | 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x64, 0x65,
73 | 0x6c, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x61, 0x72, 0x20,
74 | 0x77, 0x73, 0x3b, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
75 | 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x28, 0x29, 0x20, 0x7b,
76 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x64, 0x69, 0x76,
77 | 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
78 | 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79,
79 | 0x49, 0x64, 0x28, 0x22, 0x6d, 0x73, 0x67, 0x22, 0x29, 0x3b, 0x0a, 0x20,
80 | 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20,
81 | 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63,
82 | 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
83 | 0x28, 0x22, 0x70, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c,
84 | 0x69, 0x6e, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68,
85 | 0x69, 0x6c, 0x64, 0x28, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
86 | 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x78, 0x74, 0x4e,
87 | 0x6f, 0x64, 0x65, 0x28, 0x22, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
88 | 0x69, 0x6e, 0x67, 0x2e, 0x2e, 0x2e, 0x22, 0x29, 0x29, 0x3b, 0x0a, 0x20,
89 | 0x20, 0x20, 0x20, 0x64, 0x69, 0x76, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e,
90 | 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6c, 0x69, 0x6e, 0x65, 0x29,
91 | 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x77,
92 | 0x73, 0x20, 0x21, 0x3d, 0x20, 0x6e, 0x75, 0x6c, 0x6c, 0x29, 0x20, 0x7b,
93 | 0x0a, 0x09, 0x77, 0x73, 0x2e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x28, 0x29,
94 | 0x3b, 0x0a, 0x09, 0x77, 0x73, 0x20, 0x3d, 0x20, 0x6e, 0x75, 0x6c, 0x6c,
95 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20,
96 | 0x76, 0x61, 0x72, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x20, 0x3d,
97 | 0x20, 0x22, 0x77, 0x73, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69,
98 | 0x66, 0x20, 0x28, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6c, 0x6f,
99 | 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
100 | 0x63, 0x6f, 0x6c, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x68, 0x74, 0x74, 0x70,
101 | 0x73, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d,
102 | 0x65, 0x20, 0x3d, 0x20, 0x22, 0x77, 0x73, 0x73, 0x22, 0x3b, 0x0a, 0x20,
103 | 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x73, 0x20,
104 | 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63,
105 | 0x6b, 0x65, 0x74, 0x28, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x20, 0x2b,
106 | 0x20, 0x22, 0x3a, 0x2f, 0x2f, 0x22, 0x20, 0x2b, 0x20, 0x77, 0x69, 0x6e,
107 | 0x64, 0x6f, 0x77, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
108 | 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x20, 0x2b, 0x20, 0x22, 0x2f, 0x73, 0x6f,
109 | 0x63, 0x6b, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f,
110 | 0x20, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x69, 0x6e,
111 | 0x67, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e,
112 | 0x74, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x20, 0x69,
113 | 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x72, 0x61, 0x63, 0x79, 0x20, 0x61,
114 | 0x73, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x61, 0x73, 0x20, 0x77, 0x65,
115 | 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x69,
116 | 0x74, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x20, 0x77, 0x65, 0x20,
117 | 0x6c, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x78, 0x65, 0x63,
118 | 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x68, 0x65, 0x72, 0x65, 0x20, 0x72,
119 | 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65,
120 | 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x6f, 0x70, 0x2e,
121 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x68, 0x74, 0x74, 0x70,
122 | 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f,
123 | 0x72, 0x67, 0x2f, 0x42, 0x75, 0x67, 0x73, 0x2f, 0x50, 0x75, 0x62, 0x6c,
124 | 0x69, 0x63, 0x2f, 0x73, 0x68, 0x6f, 0x77, 0x5f, 0x62, 0x75, 0x67, 0x2e,
125 | 0x63, 0x67, 0x69, 0x3f, 0x69, 0x64, 0x3d, 0x31, 0x32, 0x35, 0x31, 0x30,
126 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x73, 0x2e, 0x6f, 0x6e, 0x6f, 0x70,
127 | 0x65, 0x6e, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f,
128 | 0x6e, 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x62, 0x61, 0x63, 0x6b,
129 | 0x6f, 0x66, 0x66, 0x5f, 0x6f, 0x6b, 0x28, 0x29, 0x3b, 0x0a, 0x09, 0x76,
130 | 0x61, 0x72, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x6f,
131 | 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74,
132 | 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x70, 0x22,
133 | 0x29, 0x3b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x61, 0x70, 0x70,
134 | 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x64, 0x6f, 0x63,
135 | 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
136 | 0x54, 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x22, 0x4f, 0x70,
137 | 0x65, 0x6e, 0x65, 0x64, 0x2e, 0x22, 0x29, 0x29, 0x3b, 0x0a, 0x09, 0x64,
138 | 0x69, 0x76, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69,
139 | 0x6c, 0x64, 0x28, 0x6c, 0x69, 0x6e, 0x65, 0x29, 0x3b, 0x0a, 0x20, 0x20,
140 | 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x73, 0x2e,
141 | 0x6f, 0x6e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20,
142 | 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x29,
143 | 0x20, 0x7b, 0x0a, 0x09, 0x2f, 0x2f, 0x20, 0x54, 0x4f, 0x44, 0x4f, 0x20,
144 | 0x63, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x77, 0x20, 0x53, 0x79,
145 | 0x6e, 0x74, 0x61, 0x78, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x0a, 0x09, 0x76,
146 | 0x61, 0x72, 0x20, 0x72, 0x70, 0x63, 0x20, 0x3d, 0x20, 0x4a, 0x53, 0x4f,
147 | 0x4e, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x28, 0x65, 0x2e, 0x64, 0x61,
148 | 0x74, 0x61, 0x29, 0x3b, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c,
149 | 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x72, 0x70, 0x63, 0x20, 0x69,
150 | 0x6e, 0x3a, 0x22, 0x2c, 0x20, 0x72, 0x70, 0x63, 0x29, 0x3b, 0x0a, 0x0a,
151 | 0x09, 0x69, 0x66, 0x20, 0x28, 0x72, 0x70, 0x63, 0x2e, 0x66, 0x6e, 0x20,
152 | 0x3d, 0x3d, 0x3d, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65,
153 | 0x64, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x7c, 0x7c, 0x20, 0x72, 0x70,
154 | 0x63, 0x2e, 0x66, 0x6e, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x22, 0x29, 0x20,
155 | 0x7b, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x72, 0x65,
156 | 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x3b, 0x20, 0x77, 0x65, 0x27,
157 | 0x72, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79,
158 | 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x72,
159 | 0x72, 0x6f, 0x72, 0x73, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65,
160 | 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x09, 0x7d, 0x0a, 0x0a, 0x09, 0x2f,
161 | 0x2f, 0x20, 0x6b, 0x6c, 0x75, 0x64, 0x67, 0x65, 0x20, 0x64, 0x69, 0x73,
162 | 0x70, 0x61, 0x74, 0x63, 0x68, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6e, 0x6f,
163 | 0x77, 0x0a, 0x09, 0x69, 0x66, 0x20, 0x28, 0x72, 0x70, 0x63, 0x2e, 0x66,
164 | 0x6e, 0x20, 0x21, 0x3d, 0x20, 0x22, 0x43, 0x68, 0x61, 0x74, 0x2e, 0x4d,
165 | 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x09,
166 | 0x20, 0x20, 0x20, 0x20, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x72, 0x72, 0x6f,
167 | 0x72, 0x20, 0x3d, 0x20, 0x7b, 0x72, 0x70, 0x63, 0x3a, 0x20, 0x22, 0x4e,
168 | 0x6f, 0x20, 0x73, 0x75, 0x63, 0x68, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74,
169 | 0x69, 0x6f, 0x6e, 0x2e, 0x22, 0x7d, 0x3b, 0x0a, 0x09, 0x20, 0x20, 0x20,
170 | 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x72, 0x70, 0x63, 0x2e,
171 | 0x66, 0x6e, 0x3b, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x6c,
172 | 0x65, 0x74, 0x65, 0x20, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x72, 0x67, 0x73,
173 | 0x3b, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74,
174 | 0x65, 0x20, 0x72, 0x70, 0x63, 0x2e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
175 | 0x3b, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x77, 0x73, 0x2e, 0x73, 0x65,
176 | 0x6e, 0x64, 0x28, 0x72, 0x70, 0x63, 0x29, 0x0a, 0x09, 0x20, 0x20, 0x20,
177 | 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x09, 0x7d, 0x0a,
178 | 0x0a, 0x09, 0x2f, 0x2f, 0x20, 0x54, 0x4f, 0x44, 0x4f, 0x20, 0x63, 0x61,
179 | 0x74, 0x63, 0x68, 0x20, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f,
180 | 0x6e, 0x73, 0x0a, 0x0a, 0x09, 0x76, 0x61, 0x72, 0x20, 0x6c, 0x69, 0x6e,
181 | 0x65, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
182 | 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65,
183 | 0x6e, 0x74, 0x28, 0x22, 0x70, 0x22, 0x29, 0x3b, 0x0a, 0x0a, 0x09, 0x76,
184 | 0x61, 0x72, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x6f,
185 | 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74,
186 | 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x73, 0x70,
187 | 0x61, 0x6e, 0x22, 0x29, 0x3b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x2e,
188 | 0x73, 0x65, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
189 | 0x28, 0x22, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x22, 0x2c, 0x20, 0x22, 0x74,
190 | 0x69, 0x6d, 0x65, 0x22, 0x29, 0x3b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65,
191 | 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64,
192 | 0x28, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72,
193 | 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64, 0x65,
194 | 0x28, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x72, 0x67, 0x73, 0x2e, 0x74, 0x69,
195 | 0x6d, 0x65, 0x29, 0x29, 0x3b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x65, 0x2e,
196 | 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28,
197 | 0x74, 0x69, 0x6d, 0x65, 0x29, 0x3b, 0x0a, 0x0a, 0x09, 0x76, 0x61, 0x72,
198 | 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75,
199 | 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45,
200 | 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x73, 0x70, 0x61, 0x6e,
201 | 0x22, 0x29, 0x3b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x2e, 0x73, 0x65,
202 | 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x22,
203 | 0x63, 0x6c, 0x61, 0x73, 0x73, 0x22, 0x2c, 0x20, 0x22, 0x66, 0x72, 0x6f,
204 | 0x6d, 0x22, 0x29, 0x3b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x2e, 0x61,
205 | 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x64,
206 | 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61,
207 | 0x74, 0x65, 0x54, 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x72,
208 | 0x70, 0x63, 0x2e, 0x61, 0x72, 0x67, 0x73, 0x2e, 0x66, 0x72, 0x6f, 0x6d,
209 | 0x29, 0x29, 0x3b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x61, 0x70,
210 | 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x66, 0x72,
211 | 0x6f, 0x6d, 0x29, 0x3b, 0x0a, 0x0a, 0x09, 0x76, 0x61, 0x72, 0x20, 0x74,
212 | 0x65, 0x78, 0x74, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65,
213 | 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65,
214 | 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x73, 0x70, 0x61, 0x6e, 0x22, 0x29,
215 | 0x3b, 0x0a, 0x09, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x73, 0x65, 0x74, 0x41,
216 | 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x22, 0x63, 0x6c,
217 | 0x61, 0x73, 0x73, 0x22, 0x2c, 0x20, 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61,
218 | 0x67, 0x65, 0x22, 0x29, 0x3b, 0x0a, 0x09, 0x74, 0x65, 0x78, 0x74, 0x2e,
219 | 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28,
220 | 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65,
221 | 0x61, 0x74, 0x65, 0x54, 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x28,
222 | 0x72, 0x70, 0x63, 0x2e, 0x61, 0x72, 0x67, 0x73, 0x2e, 0x6d, 0x65, 0x73,
223 | 0x73, 0x61, 0x67, 0x65, 0x29, 0x29, 0x3b, 0x0a, 0x09, 0x6c, 0x69, 0x6e,
224 | 0x65, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c,
225 | 0x64, 0x28, 0x74, 0x65, 0x78, 0x74, 0x29, 0x3b, 0x0a, 0x0a, 0x09, 0x64,
226 | 0x69, 0x76, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69,
227 | 0x6c, 0x64, 0x28, 0x6c, 0x69, 0x6e, 0x65, 0x29, 0x3b, 0x0a, 0x0a, 0x09,
228 | 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x72, 0x70, 0x63, 0x2e, 0x66,
229 | 0x6e, 0x3b, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x72,
230 | 0x70, 0x63, 0x2e, 0x61, 0x72, 0x67, 0x73, 0x3b, 0x0a, 0x09, 0x72, 0x70,
231 | 0x63, 0x2e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x7b,
232 | 0x7d, 0x3b, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x72,
233 | 0x70, 0x63, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x3b, 0x0a, 0x09, 0x63,
234 | 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22,
235 | 0x72, 0x70, 0x63, 0x20, 0x6f, 0x75, 0x74, 0x3a, 0x22, 0x2c, 0x20, 0x72,
236 | 0x70, 0x63, 0x29, 0x3b, 0x0a, 0x09, 0x77, 0x73, 0x2e, 0x73, 0x65, 0x6e,
237 | 0x64, 0x28, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e,
238 | 0x67, 0x69, 0x66, 0x79, 0x28, 0x72, 0x70, 0x63, 0x29, 0x29, 0x3b, 0x0a,
239 | 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77,
240 | 0x73, 0x2e, 0x6f, 0x6e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x20, 0x3d, 0x20,
241 | 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x29,
242 | 0x20, 0x7b, 0x0a, 0x09, 0x76, 0x61, 0x72, 0x20, 0x6c, 0x69, 0x6e, 0x65,
243 | 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
244 | 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
245 | 0x74, 0x28, 0x22, 0x70, 0x22, 0x29, 0x3b, 0x0a, 0x09, 0x6c, 0x69, 0x6e,
246 | 0x65, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c,
247 | 0x64, 0x28, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63,
248 | 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64,
249 | 0x65, 0x28, 0x22, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x2e, 0x22, 0x29,
250 | 0x29, 0x3b, 0x0a, 0x09, 0x64, 0x69, 0x76, 0x2e, 0x61, 0x70, 0x70, 0x65,
251 | 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6c, 0x69, 0x6e, 0x65,
252 | 0x29, 0x3b, 0x0a, 0x0a, 0x09, 0x2f, 0x2f, 0x20, 0x49, 0x66, 0x20, 0x74,
253 | 0x68, 0x69, 0x73, 0x20, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65,
254 | 0x74, 0x20, 0x69, 0x73, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x74,
255 | 0x68, 0x65, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x6f, 0x6e, 0x65, 0x2c,
256 | 0x20, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x3b, 0x0a,
257 | 0x09, 0x2f, 0x2f, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73,
258 | 0x65, 0x2c, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x69, 0x74, 0x20, 0x64, 0x69,
259 | 0x65, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x6f,
260 | 0x77, 0x73, 0x20, 0x65, 0x61, 0x73, 0x79, 0x20, 0x66, 0x69, 0x64, 0x64,
261 | 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x6a, 0x73,
262 | 0x0a, 0x09, 0x2f, 0x2f, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65,
263 | 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x60,
264 | 0x60, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x28, 0x29, 0x60, 0x60,
265 | 0x2e, 0x20, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6f,
266 | 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
267 | 0x0a, 0x09, 0x2f, 0x2f, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
268 | 0x73, 0x20, 0x67, 0x65, 0x74, 0x73, 0x20, 0x77, 0x65, 0x69, 0x72, 0x64,
269 | 0x2c, 0x20, 0x74, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x2e, 0x0a, 0x09, 0x69,
270 | 0x66, 0x20, 0x28, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3d, 0x3d, 0x3d, 0x20,
271 | 0x77, 0x73, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x76,
272 | 0x61, 0x72, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x62,
273 | 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x75,
274 | 0x74, 0x65, 0x28, 0x29, 0x3b, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x73,
275 | 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x28, 0x63, 0x6f,
276 | 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2c, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79,
277 | 0x29, 0x3b, 0x0a, 0x09, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b,
278 | 0x0a, 0x7d, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20,
279 | 0x69, 0x6e, 0x69, 0x74, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
280 | 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x6d, 0x73,
281 | 0x67, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
282 | 0x65, 0x2e, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x28, 0x29, 0x3b, 0x0a, 0x20,
283 | 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x28, 0x29,
284 | 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
285 | 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x28, 0x29, 0x20, 0x7b, 0x0a,
286 | 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x72, 0x70, 0x63, 0x20,
287 | 0x3d, 0x20, 0x7b, 0x0a, 0x09, 0x2f, 0x2f, 0x20, 0x54, 0x4f, 0x44, 0x4f,
288 | 0x0a, 0x09, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x30, 0x22, 0x2c, 0x0a, 0x09,
289 | 0x66, 0x6e, 0x3a, 0x20, 0x22, 0x43, 0x68, 0x61, 0x74, 0x2e, 0x4d, 0x65,
290 | 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x0a, 0x09, 0x61, 0x72, 0x67,
291 | 0x73, 0x3a, 0x20, 0x7b, 0x0a, 0x09, 0x20, 0x20, 0x20, 0x20, 0x66, 0x72,
292 | 0x6f, 0x6d, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
293 | 0x2e, 0x6d, 0x73, 0x67, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x6e, 0x61, 0x6d,
294 | 0x65, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0a, 0x09, 0x20, 0x20,
295 | 0x20, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x20, 0x64,
296 | 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x6d, 0x73, 0x67, 0x66,
297 | 0x6f, 0x72, 0x6d, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e,
298 | 0x76, 0x61, 0x6c, 0x75, 0x65, 0x0a, 0x09, 0x7d, 0x0a, 0x20, 0x20, 0x20,
299 | 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73,
300 | 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x72, 0x70, 0x63,
301 | 0x20, 0x6f, 0x75, 0x74, 0x3a, 0x22, 0x2c, 0x20, 0x72, 0x70, 0x63, 0x29,
302 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x73, 0x2e, 0x73, 0x65, 0x6e,
303 | 0x64, 0x28, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e,
304 | 0x67, 0x69, 0x66, 0x79, 0x28, 0x72, 0x70, 0x63, 0x29, 0x29, 0x3b, 0x0a,
305 | 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
306 | 0x2e, 0x6d, 0x73, 0x67, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x6d, 0x65, 0x73,
307 | 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d,
308 | 0x20, 0x22, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74,
309 | 0x75, 0x72, 0x6e, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x0a, 0x7d,
310 | 0x3b, 0x0a,
311 | }
312 |
--------------------------------------------------------------------------------
/examples/jankybrowser/browser.html.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | var browser_html = []byte{
4 | 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x68, 0x65,
5 | 0x61, 0x64, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x73, 0x74, 0x79,
6 | 0x6c, 0x65, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x74,
7 | 0x6d, 0x6c, 0x2c, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x2c, 0x0a, 0x20, 0x20,
8 | 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x7b,
9 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6c, 0x65,
10 | 0x61, 0x72, 0x3a, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20,
11 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e,
12 | 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
13 | 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x3b,
14 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64,
15 | 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a, 0x20, 0x20,
16 | 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
17 | 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20,
18 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72,
19 | 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78,
20 | 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x67, 0x72, 0x61, 0x79, 0x3b,
21 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20,
22 | 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x69,
23 | 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
24 | 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x38,
25 | 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a,
26 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x23, 0x6e, 0x61, 0x76, 0x20,
27 | 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c,
28 | 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20,
29 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
30 | 0x20, 0x23, 0x6e, 0x61, 0x76, 0x20, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65,
31 | 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66,
32 | 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a,
33 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
34 | 0x20, 0x20, 0x20, 0x23, 0x6e, 0x61, 0x76, 0x20, 0x2e, 0x66, 0x6f, 0x72,
35 | 0x77, 0x61, 0x72, 0x64, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
36 | 0x23, 0x6e, 0x61, 0x76, 0x20, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x77, 0x61,
37 | 0x72, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
38 | 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74,
39 | 0x3a, 0x20, 0x62, 0x6f, 0x6c, 0x64, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
40 | 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6c,
41 | 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
42 | 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x38, 0x70,
43 | 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
44 | 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20,
45 | 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
46 | 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x23, 0x74, 0x61,
47 | 0x62, 0x73, 0x2d, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x2c, 0x20,
48 | 0x23, 0x74, 0x61, 0x62, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
49 | 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20,
50 | 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
51 | 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20,
52 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
53 | 0x20, 0x20, 0x23, 0x74, 0x61, 0x62, 0x73, 0x2d, 0x77, 0x72, 0x61, 0x70,
54 | 0x70, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
55 | 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30,
56 | 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
57 | 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x3a, 0x20, 0x68, 0x69,
58 | 0x64, 0x64, 0x65, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
59 | 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x74, 0x61,
60 | 0x62, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
61 | 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30,
62 | 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c,
63 | 0x69, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e,
64 | 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
65 | 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x30, 0x3b, 0x0a,
66 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64,
67 | 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
68 | 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77,
69 | 0x3a, 0x20, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x3b, 0x0a, 0x20, 0x20,
70 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a,
71 | 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
72 | 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x74,
73 | 0x61, 0x62, 0x73, 0x20, 0x6c, 0x69, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
74 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20,
75 | 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
76 | 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68,
77 | 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20,
78 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e,
79 | 0x67, 0x3a, 0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20,
80 | 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
81 | 0x2e, 0x74, 0x61, 0x62, 0x73, 0x20, 0x6c, 0x69, 0x2e, 0x61, 0x63, 0x74,
82 | 0x69, 0x76, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
83 | 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64,
84 | 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x67, 0x72, 0x61, 0x79,
85 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20,
86 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x2e, 0x74, 0x61, 0x62, 0x20,
87 | 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f,
88 | 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f,
89 | 0x6c, 0x69, 0x64, 0x20, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x3b, 0x0a, 0x20,
90 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
91 | 0x20, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x2d,
92 | 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
93 | 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20,
94 | 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20,
95 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20,
96 | 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
97 | 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65,
98 | 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20,
99 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x20, 0x35,
100 | 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
101 | 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x6c, 0x65, 0x66, 0x74,
102 | 0x3a, 0x20, 0x33, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
103 | 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a,
104 | 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x3b, 0x0a, 0x20,
105 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x70, 0x3a, 0x20,
106 | 0x2d, 0x38, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
107 | 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x23, 0x6e, 0x65,
108 | 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
109 | 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x73, 0x6f, 0x6c, 0x69,
110 | 0x64, 0x20, 0x31, 0x70, 0x78, 0x20, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a,
111 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73,
112 | 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b,
113 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f,
114 | 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20,
115 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74,
116 | 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74,
117 | 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72,
118 | 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0a,
119 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
120 | 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74,
121 | 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
122 | 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20,
123 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
124 | 0x3a, 0x20, 0x32, 0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
125 | 0x20, 0x20, 0x20, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d,
126 | 0x75, 0x73, 0x65, 0x72, 0x2d, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x3a,
127 | 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
128 | 0x20, 0x20, 0x20, 0x7a, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x20,
129 | 0x31, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
130 | 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x23, 0x70, 0x61,
131 | 0x67, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x7b,
132 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69,
133 | 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a, 0x20,
134 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
135 | 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
136 | 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x23,
137 | 0x70, 0x61, 0x67, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
138 | 0x20, 0x2e, 0x70, 0x61, 0x67, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
139 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a,
140 | 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
141 | 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f,
142 | 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x20, 0x30, 0x2e, 0x37, 0x73,
143 | 0x20, 0x65, 0x61, 0x73, 0x65, 0x2d, 0x69, 0x6e, 0x2d, 0x6f, 0x75, 0x74,
144 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f,
145 | 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x62, 0x73, 0x6f,
146 | 0x6c, 0x75, 0x74, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
147 | 0x20, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x20, 0x2d, 0x31, 0x30, 0x30,
148 | 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77,
149 | 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a,
150 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
151 | 0x20, 0x20, 0x20, 0x23, 0x70, 0x61, 0x67, 0x65, 0x2d, 0x63, 0x6f, 0x6e,
152 | 0x74, 0x65, 0x6e, 0x74, 0x20, 0x2e, 0x70, 0x61, 0x67, 0x65, 0x2e, 0x61,
153 | 0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
154 | 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x20, 0x30, 0x3b,
155 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20,
156 | 0x20, 0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3e, 0x0a, 0x20, 0x20,
157 | 0x20, 0x20, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x74, 0x79,
158 | 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x6a, 0x61, 0x76,
159 | 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x3e, 0x0a, 0x0a, 0x66,
160 | 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x75, 0x72, 0x6c, 0x54,
161 | 0x6f, 0x54, 0x74, 0x69, 0x6c, 0x65, 0x28, 0x75, 0x72, 0x6c, 0x29, 0x20,
162 | 0x7b, 0x0a, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x61, 0x20, 0x3d, 0x20,
163 | 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65,
164 | 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22,
165 | 0x61, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x2e, 0x68, 0x72, 0x65,
166 | 0x66, 0x20, 0x3d, 0x20, 0x75, 0x72, 0x6c, 0x3b, 0x0a, 0x0a, 0x20, 0x20,
167 | 0x76, 0x61, 0x72, 0x20, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65,
168 | 0x20, 0x3d, 0x20, 0x61, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,
169 | 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,
170 | 0x65, 0x20, 0x3d, 0x20, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65,
171 | 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x28, 0x22, 0x2e, 0x22, 0x29, 0x2e,
172 | 0x73, 0x6c, 0x69, 0x63, 0x65, 0x28, 0x2d, 0x32, 0x29, 0x2e, 0x6a, 0x6f,
173 | 0x69, 0x6e, 0x28, 0x22, 0x2e, 0x22, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20,
174 | 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x68, 0x6f, 0x73, 0x74, 0x6e,
175 | 0x61, 0x6d, 0x65, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x61, 0x72, 0x20,
176 | 0x6c, 0x6f, 0x63, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x20, 0x74, 0x61, 0x62,
177 | 0x73, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x20, 0x6e, 0x65, 0x77, 0x54, 0x61,
178 | 0x62, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x20, 0x70, 0x61, 0x67, 0x65, 0x43,
179 | 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x20,
180 | 0x6e, 0x61, 0x76, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x3b, 0x0a,
181 | 0x76, 0x61, 0x72, 0x20, 0x6e, 0x61, 0x76, 0x42, 0x61, 0x63, 0x6b, 0x77,
182 | 0x61, 0x72, 0x64, 0x3b, 0x0a, 0x0a, 0x76, 0x61, 0x72, 0x20, 0x61, 0x63,
183 | 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x67, 0x65, 0x3b, 0x0a, 0x76, 0x61,
184 | 0x72, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x61, 0x62, 0x3b,
185 | 0x0a, 0x76, 0x61, 0x72, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x57,
186 | 0x56, 0x3b, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
187 | 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x28, 0x70, 0x61,
188 | 0x67, 0x65, 0x2c, 0x20, 0x74, 0x61, 0x62, 0x29, 0x20, 0x7b, 0x0a, 0x20,
189 | 0x20, 0x69, 0x66, 0x28, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61,
190 | 0x67, 0x65, 0x20, 0x26, 0x26, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65,
191 | 0x50, 0x61, 0x67, 0x65, 0x20, 0x21, 0x3d, 0x3d, 0x20, 0x70, 0x61, 0x67,
192 | 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74,
193 | 0x69, 0x76, 0x65, 0x50, 0x61, 0x67, 0x65, 0x2e, 0x63, 0x6c, 0x61, 0x73,
194 | 0x73, 0x4c, 0x69, 0x73, 0x74, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65,
195 | 0x28, 0x22, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x3b, 0x0a,
196 | 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x28, 0x61, 0x63,
197 | 0x74, 0x69, 0x76, 0x65, 0x54, 0x61, 0x62, 0x20, 0x26, 0x26, 0x20, 0x61,
198 | 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x61, 0x62, 0x20, 0x21, 0x3d, 0x3d,
199 | 0x20, 0x74, 0x61, 0x62, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
200 | 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x61, 0x62, 0x2e, 0x63, 0x6c,
201 | 0x61, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x2e, 0x72, 0x65, 0x6d, 0x6f,
202 | 0x76, 0x65, 0x28, 0x22, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29,
203 | 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x61, 0x63, 0x74,
204 | 0x69, 0x76, 0x65, 0x50, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x70, 0x61,
205 | 0x67, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65,
206 | 0x54, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x74, 0x61, 0x62, 0x3b, 0x0a, 0x20,
207 | 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x57, 0x56, 0x20, 0x3d, 0x20,
208 | 0x70, 0x61, 0x67, 0x65, 0x2e, 0x66, 0x69, 0x72, 0x73, 0x74, 0x45, 0x6c,
209 | 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x3b, 0x0a,
210 | 0x0a, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x67,
211 | 0x65, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x2e,
212 | 0x61, 0x64, 0x64, 0x28, 0x22, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22,
213 | 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54,
214 | 0x61, 0x62, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74,
215 | 0x2e, 0x61, 0x64, 0x64, 0x28, 0x22, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65,
216 | 0x22, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x2e, 0x76,
217 | 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76,
218 | 0x65, 0x57, 0x56, 0x2e, 0x73, 0x72, 0x63, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a,
219 | 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, 0x6e, 0x6c, 0x6f, 0x61,
220 | 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
221 | 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x24,
222 | 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
223 | 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f,
224 | 0x72, 0x2e, 0x62, 0x69, 0x6e, 0x64, 0x28, 0x64, 0x6f, 0x63, 0x75, 0x6d,
225 | 0x65, 0x6e, 0x74, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63,
226 | 0x20, 0x3d, 0x20, 0x24, 0x28, 0x22, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
227 | 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20,
228 | 0x74, 0x61, 0x62, 0x73, 0x20, 0x3d, 0x20, 0x24, 0x28, 0x22, 0x75, 0x6c,
229 | 0x2e, 0x74, 0x61, 0x62, 0x73, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x6e,
230 | 0x65, 0x77, 0x54, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x24, 0x28, 0x22, 0x23,
231 | 0x6e, 0x65, 0x77, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x67,
232 | 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x24,
233 | 0x28, 0x22, 0x23, 0x70, 0x61, 0x67, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74,
234 | 0x65, 0x6e, 0x74, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x6e, 0x61, 0x76,
235 | 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x24, 0x28,
236 | 0x22, 0x23, 0x6e, 0x61, 0x76, 0x20, 0x2e, 0x66, 0x6f, 0x72, 0x77, 0x61,
237 | 0x72, 0x64, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x6e, 0x61, 0x76, 0x42,
238 | 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x24, 0x28,
239 | 0x22, 0x23, 0x6e, 0x61, 0x76, 0x20, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x77,
240 | 0x61, 0x72, 0x64, 0x22, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x66, 0x75,
241 | 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x4e,
242 | 0x65, 0x77, 0x54, 0x61, 0x62, 0x28, 0x75, 0x72, 0x6c, 0x29, 0x20, 0x7b,
243 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x70, 0x61, 0x67,
244 | 0x65, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
245 | 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65,
246 | 0x6e, 0x74, 0x28, 0x22, 0x64, 0x69, 0x76, 0x22, 0x29, 0x3b, 0x0a, 0x20,
247 | 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x2e, 0x63, 0x6c, 0x61, 0x73,
248 | 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x70, 0x61, 0x67,
249 | 0x65, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72,
250 | 0x20, 0x77, 0x65, 0x62, 0x76, 0x69, 0x65, 0x77, 0x20, 0x3d, 0x20, 0x64,
251 | 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61,
252 | 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x77,
253 | 0x65, 0x62, 0x76, 0x69, 0x65, 0x77, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20,
254 | 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e,
255 | 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x77, 0x65, 0x62, 0x76, 0x69,
256 | 0x65, 0x77, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61,
257 | 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x61, 0x70,
258 | 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x70, 0x61,
259 | 0x67, 0x65, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66,
260 | 0x28, 0x75, 0x72, 0x6c, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
261 | 0x20, 0x20, 0x77, 0x65, 0x62, 0x76, 0x69, 0x65, 0x77, 0x2e, 0x73, 0x72,
262 | 0x63, 0x20, 0x3d, 0x20, 0x75, 0x72, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20,
263 | 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20,
264 | 0x74, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65,
265 | 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65,
266 | 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x6c, 0x69, 0x22, 0x29, 0x3b, 0x0a,
267 | 0x20, 0x20, 0x20, 0x20, 0x74, 0x61, 0x62, 0x2e, 0x63, 0x6c, 0x61, 0x73,
268 | 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x74, 0x61, 0x62,
269 | 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20,
270 | 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75,
271 | 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54,
272 | 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x75, 0x72, 0x6c, 0x54,
273 | 0x6f, 0x54, 0x74, 0x69, 0x6c, 0x65, 0x28, 0x75, 0x72, 0x6c, 0x29, 0x29,
274 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x61, 0x62, 0x2e, 0x61, 0x70,
275 | 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6c, 0x61,
276 | 0x62, 0x65, 0x6c, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76,
277 | 0x61, 0x72, 0x20, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x20, 0x3d, 0x20, 0x64,
278 | 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61,
279 | 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x64,
280 | 0x69, 0x76, 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6c,
281 | 0x6f, 0x73, 0x65, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4e, 0x61, 0x6d,
282 | 0x65, 0x20, 0x3d, 0x20, 0x22, 0x74, 0x61, 0x62, 0x2d, 0x63, 0x6c, 0x6f,
283 | 0x73, 0x65, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6c, 0x6f,
284 | 0x73, 0x65, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65,
285 | 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x22, 0x58, 0x22, 0x3b, 0x0a, 0x20, 0x20,
286 | 0x20, 0x20, 0x74, 0x61, 0x62, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64,
287 | 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x29,
288 | 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x61, 0x62, 0x73, 0x2e,
289 | 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28,
290 | 0x74, 0x61, 0x62, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
291 | 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x68, 0x61, 0x6e, 0x64,
292 | 0x6c, 0x65, 0x54, 0x61, 0x62, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x28, 0x29,
293 | 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e,
294 | 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x63, 0x6c,
295 | 0x69, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x61, 0x62, 0x22, 0x29,
296 | 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69,
297 | 0x76, 0x61, 0x74, 0x65, 0x28, 0x70, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x74,
298 | 0x61, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a,
299 | 0x20, 0x20, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
300 | 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65,
301 | 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x28, 0x65, 0x76, 0x74, 0x29, 0x20, 0x7b,
302 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f,
303 | 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x63, 0x6c, 0x6f, 0x73,
304 | 0x69, 0x6e, 0x67, 0x20, 0x74, 0x61, 0x62, 0x22, 0x29, 0x3b, 0x0a, 0x20,
305 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x61, 0x62, 0x2e, 0x72, 0x65, 0x6d,
306 | 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74,
307 | 0x65, 0x6e, 0x65, 0x72, 0x28, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x54,
308 | 0x61, 0x62, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x29, 0x3b, 0x0a, 0x20, 0x20,
309 | 0x20, 0x20, 0x20, 0x20, 0x77, 0x65, 0x62, 0x76, 0x69, 0x65, 0x77, 0x2e,
310 | 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c,
311 | 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x68, 0x61, 0x6e, 0x64,
312 | 0x6c, 0x65, 0x54, 0x61, 0x62, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x29, 0x3b,
313 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6c, 0x6f, 0x73, 0x65,
314 | 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74,
315 | 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x68, 0x61, 0x6e,
316 | 0x64, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6c, 0x69, 0x63,
317 | 0x6b, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74,
318 | 0x61, 0x62, 0x2e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64,
319 | 0x65, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, 0x69, 0x6c,
320 | 0x64, 0x28, 0x74, 0x61, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
321 | 0x20, 0x20, 0x77, 0x65, 0x62, 0x76, 0x69, 0x65, 0x77, 0x2e, 0x70, 0x61,
322 | 0x72, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x72, 0x65, 0x6d,
323 | 0x6f, 0x76, 0x65, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x77, 0x65, 0x62,
324 | 0x76, 0x69, 0x65, 0x77, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
325 | 0x20, 0x20, 0x65, 0x76, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x70, 0x50, 0x72,
326 | 0x6f, 0x70, 0x61, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x3b,
327 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
328 | 0x74, 0x61, 0x62, 0x2e, 0x61, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74,
329 | 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x22, 0x63, 0x6c,
330 | 0x69, 0x63, 0x6b, 0x22, 0x2c, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65,
331 | 0x54, 0x61, 0x62, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x2c, 0x20, 0x66, 0x61,
332 | 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6c,
333 | 0x6f, 0x73, 0x65, 0x2e, 0x61, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74,
334 | 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x22, 0x63, 0x6c,
335 | 0x69, 0x63, 0x6b, 0x22, 0x2c, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65,
336 | 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x2c, 0x20,
337 | 0x74, 0x72, 0x75, 0x65, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
338 | 0x77, 0x65, 0x62, 0x76, 0x69, 0x65, 0x77, 0x2e, 0x61, 0x64, 0x64, 0x45,
339 | 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72,
340 | 0x28, 0x22, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x2d, 0x73, 0x65, 0x74, 0x22,
341 | 0x2c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x65,
342 | 0x76, 0x74, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
343 | 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x43, 0x6f,
344 | 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x65, 0x76, 0x74, 0x2e,
345 | 0x74, 0x69, 0x74, 0x6c, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
346 | 0x2c, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x0a, 0x0a, 0x20,
347 | 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x28,
348 | 0x70, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x74, 0x61, 0x62, 0x29, 0x3b, 0x0a,
349 | 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x2e, 0x61,
350 | 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65,
351 | 0x6e, 0x65, 0x72, 0x28, 0x22, 0x6b, 0x65, 0x79, 0x70, 0x72, 0x65, 0x73,
352 | 0x73, 0x22, 0x2c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
353 | 0x28, 0x65, 0x76, 0x74, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
354 | 0x69, 0x66, 0x28, 0x65, 0x76, 0x74, 0x2e, 0x6b, 0x65, 0x79, 0x43, 0x6f,
355 | 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x3d, 0x20, 0x31, 0x33, 0x29, 0x20, 0x7b,
356 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f,
357 | 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x63, 0x68, 0x61, 0x6e,
358 | 0x67, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
359 | 0x6e, 0x20, 0x22, 0x20, 0x2b, 0x20, 0x6c, 0x6f, 0x63, 0x2e, 0x76, 0x61,
360 | 0x6c, 0x75, 0x65, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
361 | 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x57, 0x56, 0x2e, 0x73, 0x72,
362 | 0x63, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x63, 0x2e, 0x76, 0x61, 0x6c, 0x75,
363 | 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x7d,
364 | 0x2c, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x0a, 0x0a, 0x20,
365 | 0x20, 0x6e, 0x65, 0x77, 0x54, 0x61, 0x62, 0x2e, 0x61, 0x64, 0x64, 0x45,
366 | 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72,
367 | 0x28, 0x22, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x22, 0x2c, 0x20, 0x66, 0x75,
368 | 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20,
369 | 0x20, 0x20, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x4e, 0x65, 0x77, 0x54, 0x61,
370 | 0x62, 0x28, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
371 | 0x77, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
372 | 0x22, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x20, 0x66, 0x61, 0x6c,
373 | 0x73, 0x65, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6e, 0x61, 0x76, 0x42,
374 | 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x2e, 0x61, 0x64, 0x64, 0x45,
375 | 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72,
376 | 0x28, 0x22, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x22, 0x2c, 0x20, 0x66, 0x75,
377 | 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20,
378 | 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x57, 0x56, 0x2e,
379 | 0x62, 0x61, 0x63, 0x6b, 0x28, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x29,
380 | 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6e, 0x61, 0x76, 0x46, 0x6f, 0x72, 0x77,
381 | 0x61, 0x72, 0x64, 0x2e, 0x61, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74,
382 | 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x22, 0x63, 0x6c,
383 | 0x69, 0x63, 0x6b, 0x22, 0x2c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
384 | 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61,
385 | 0x63, 0x74, 0x69, 0x76, 0x65, 0x57, 0x56, 0x2e, 0x66, 0x6f, 0x72, 0x77,
386 | 0x61, 0x72, 0x64, 0x28, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x29, 0x3b,
387 | 0x0a, 0x0a, 0x20, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x4e, 0x65, 0x77, 0x54,
388 | 0x61, 0x62, 0x28, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
389 | 0x77, 0x77, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
390 | 0x6d, 0x22, 0x29, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20,
391 | 0x20, 0x3c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0a, 0x20,
392 | 0x20, 0x3c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x3e, 0x0a, 0x20, 0x20, 0x3c,
393 | 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68,
394 | 0x65, 0x61, 0x64, 0x65, 0x72, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
395 | 0x20, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x6e, 0x61,
396 | 0x76, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c,
397 | 0x69, 0x6d, 0x67, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3d, 0x22,
398 | 0x35, 0x30, 0x70, 0x78, 0x22, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3d,
399 | 0x22, 0x31, 0x32, 0x30, 0x70, 0x78, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73,
400 | 0x73, 0x3d, 0x22, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x20, 0x73, 0x72,
401 | 0x63, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x69, 0x2e,
402 | 0x69, 0x6d, 0x67, 0x75, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x77,
403 | 0x46, 0x4b, 0x49, 0x30, 0x4a, 0x2e, 0x70, 0x6e, 0x67, 0x22, 0x2f, 0x3e,
404 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x64, 0x69,
405 | 0x76, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x62, 0x61, 0x63,
406 | 0x6b, 0x77, 0x61, 0x72, 0x64, 0x22, 0x3e, 0x26, 0x6c, 0x74, 0x3b, 0x3c,
407 | 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
408 | 0x20, 0x20, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73,
409 | 0x3d, 0x22, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x22, 0x3e, 0x26,
410 | 0x67, 0x74, 0x3b, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0a, 0x20, 0x20,
411 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74,
412 | 0x20, 0x69, 0x64, 0x3d, 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
413 | 0x6e, 0x22, 0x20, 0x2f, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
414 | 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
415 | 0x20, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x74, 0x61,
416 | 0x62, 0x73, 0x2d, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x22, 0x3e,
417 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x75, 0x6c,
418 | 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x74, 0x61, 0x62, 0x73,
419 | 0x22, 0x3e, 0x3c, 0x2f, 0x75, 0x6c, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20,
420 | 0x20, 0x20, 0x20, 0x20, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d,
421 | 0x22, 0x6e, 0x65, 0x77, 0x22, 0x3e, 0x2b, 0x3c, 0x2f, 0x64, 0x69, 0x76,
422 | 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x69,
423 | 0x76, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x68, 0x65, 0x61,
424 | 0x64, 0x65, 0x72, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x64, 0x69,
425 | 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x70, 0x61, 0x67, 0x65, 0x2d, 0x63,
426 | 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x3e, 0x3c, 0x2f, 0x64, 0x69,
427 | 0x76, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e,
428 | 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0a,
429 | }
430 |
--------------------------------------------------------------------------------