support for x2x and barrier KVM

This commit is contained in:
2021-10-10 00:09:37 +04:00
parent ce61920ec2
commit 0ed316cb5e
11 changed files with 831 additions and 814 deletions

View File

@@ -24,15 +24,31 @@ https://github.com/Akilan1999/laplace/tree/keyboard_mouse
### Installation required to share keyboard and mouse ### Installation required to share keyboard and mouse
We need to ensure that the client has SSH client installed. Currently, you can either use [x2x](https://github.com/dottedmag/x2x) or [Barrier KVM]()
We use the popular open repository known as [x2x](https://github.com/dottedmag/x2x). We need to ensure that the client has SSH client installed or Barrierc.
Note: x2x runs on top of SSH.
#### What is x2x? #### What is x2x?
x2x allows the keyboard, mouse on one X display to be used to control another X x2x allows the keyboard, mouse on one X display to be used to control another X
display. It also shares X clipboards between the displays. display. It also shares X clipboards between the displays.
Note: x2x runs on top of SSH.
#### What is Barrier kvm?
Barrier is software that mimics the functionality of a KVM switch, which historically would allow you to use a single
keyboard and mouse to control multiple computers by physically turning a dial on the box to switch the machine you're
controlling at any given moment. Barrier does this in software, allowing you to tell it which machine to control by
moving your mouse to the edge of the screen, or by using a keypress to switch focus to a different system.
#### Barrier KVM build status and links to install
|Platform |Build Status|
| --:|:-- |
|Linux |[![Build Status](https://dev.azure.com/debauchee/Barrier/_apis/build/status/debauchee.barrier?branchName=master&jobName=Linux%20Build)](https://dev.azure.com/debauchee/Barrier/_build/latest?definitionId=1&branchName=master)|
|Mac |[![Build Status](https://dev.azure.com/debauchee/Barrier/_apis/build/status/debauchee.barrier?branchName=master&jobName=Mac%20Build)](https://dev.azure.com/debauchee/Barrier/_build/latest?definitionId=1&branchName=master)|
|Windows Debug |[![Build Status](https://dev.azure.com/debauchee/Barrier/_apis/build/status/debauchee.barrier?branchName=master&jobName=Windows%20Build&configuration=Windows%20Build%20Debug)](https://dev.azure.com/debauchee/Barrier/_build/latest?definitionId=1&branchName=master)|
|Windows Release|[![Build Status](https://dev.azure.com/debauchee/Barrier/_apis/build/status/debauchee.barrier?branchName=master&jobName=Windows%20Build&configuration=Windows%20Build%20Release%20with%20Release%20Installer)](https://dev.azure.com/debauchee/Barrier/_build/latest?definitionId=1&branchName=master)|
|Snap |[![Snap Status](https://build.snapcraft.io/badge/debauchee/barrier.svg)](https://build.snapcraft.io/user/debauchee/barrier)|
### Build from source ### Build from source
@@ -51,6 +67,8 @@ apt install -y git
apt install -y openssh-server apt install -y openssh-server
## Installing x2x ## Installing x2x
apt install -y x2x apt install -y x2x
## Installing barrier
apt install -y barrier
## Installing chromium ## Installing chromium
wget https://github.com/RobRich999/Chromium_Clang/releases/download/v94.0.4585.0-r904940-linux64-deb-avx/chromium-browser-unstable_94.0.4585.0-1_amd64.deb wget https://github.com/RobRich999/Chromium_Clang/releases/download/v94.0.4585.0-r904940-linux64-deb-avx/chromium-browser-unstable_94.0.4585.0-1_amd64.deb
apt install -y ./chromium-browser-unstable_94.0.4585.0-1_amd64.deb apt install -y ./chromium-browser-unstable_94.0.4585.0-1_amd64.deb
@@ -117,11 +135,13 @@ cd /path/.local/share/Steam/steamapps/common/X-Plane\ 11/
``` ```
#### Open config file #### Open config file
```bash ```bash
{ {
"barrierhostname": "<barrier host name>", "barrierhostname": "<barrier host name>",
"ipaddress": "0.0.0.0", "ipaddress": "0.0.0.0",
"rooms": "<path to room.json file>", "rooms": "<path to room.json file>",
"scripttoexecute": "<path to script to execute (In case the Xplane 11 script)>", "scripttoexecute": "<path to script to execute (In case the Xplane 11 script)>",
"sshpassword": "<SSH password for x2x>",
"systemusername": "<system username>" "systemusername": "<system username>"
} }
``` ```

View File

@@ -8,23 +8,23 @@ import (
var ( var (
defaultPath string defaultPath string
defaults = map[string]interface{}{ defaults = map[string]interface{}{
"SystemUsername": "", "SystemUsername": "",
"BarrierHostName": "", "BarrierHostName": "",
} }
configName = "config" configName = "config"
configType = "json" configType = "json"
configFile = "config.json" configFile = "config.json"
configPaths []string configPaths []string
) )
type Config struct { type Config struct {
SystemUsername string SystemUsername string
BarrierHostName string BarrierHostName string
Rooms string Rooms string
IPAddress string IPAddress string
ScriptToExecute string ScriptToExecute string
SSHPassword string SSHPassword string
} }
// Exists reports whether the named file or directory exists. // Exists reports whether the named file or directory exists.
@@ -47,7 +47,7 @@ func SetDefaults() error {
defaultPath = curDir + "/" defaultPath = curDir + "/"
// Get system username // Get system username
user ,err := user.Current() user, err := user.Current()
if err != nil { if err != nil {
return err return err
} }
@@ -59,8 +59,8 @@ func SetDefaults() error {
} }
//Setting default paths for the config file //Setting default paths for the config file
defaults["SystemUsername"] = user.Username defaults["SystemUsername"] = user.Username
defaults["BarrierHostName"] = name defaults["BarrierHostName"] = name
defaults["Rooms"] = defaultPath + "room.json" defaults["Rooms"] = defaultPath + "room.json"
defaults["IPAddress"] = "0.0.0.0" defaults["IPAddress"] = "0.0.0.0"
defaults["ScriptToExecute"] = "" defaults["ScriptToExecute"] = ""
@@ -88,7 +88,7 @@ func SetDefaults() error {
return nil return nil
} }
func ConfigInit()(*Config,error) { func ConfigInit() (*Config, error) {
curDir := os.Getenv("REMOTEGAMING") curDir := os.Getenv("REMOTEGAMING")
//Setting current directory to default path //Setting current directory to default path
@@ -97,7 +97,7 @@ func ConfigInit()(*Config,error) {
configPaths = append(configPaths, defaultPath) configPaths = append(configPaths, defaultPath)
//Add all possible configurations paths //Add all possible configurations paths
for _,v := range configPaths { for _, v := range configPaths {
viper.AddConfigPath(v) viper.AddConfigPath(v)
} }
@@ -105,23 +105,23 @@ func ConfigInit()(*Config,error) {
if err := viper.ReadInConfig(); err != nil { if err := viper.ReadInConfig(); err != nil {
// If the error thrown is config file not found // If the error thrown is config file not found
//Sets default configuration to viper //Sets default configuration to viper
for k,v := range defaults { for k, v := range defaults {
viper.SetDefault(k,v) viper.SetDefault(k, v)
} }
viper.SetConfigName(configName) viper.SetConfigName(configName)
viper.SetConfigFile(configFile) viper.SetConfigFile(configFile)
viper.SetConfigType(configType) viper.SetConfigType(configType)
if err = viper.WriteConfig(); err != nil { if err = viper.WriteConfig(); err != nil {
return nil,err return nil, err
} }
} }
// Adds configuration to the struct // Adds configuration to the struct
var config Config var config Config
if err := viper.Unmarshal(&config); err != nil { if err := viper.Unmarshal(&config); err != nil {
return nil,err return nil, err
} }
return &config,nil return &config, nil
} }

View File

@@ -1,4 +1,3 @@
package config package config
import ( import (
@@ -6,7 +5,7 @@ import (
) )
func TestConfigInit(t *testing.T) { func TestConfigInit(t *testing.T) {
_,err := ConfigInit() _, err := ConfigInit()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@@ -17,4 +16,4 @@ func TestSetDefaults(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
} }

View File

@@ -5,20 +5,20 @@
package core package core
import ( import (
"laplace/config" "github.com/Akilan1999/remotegameplay/config"
"os/exec" "os/exec"
) )
// Barrier It's preferred that the IP address used is a IPV6 address // Barrier It's preferred that the IP address used is a IPV6 address
type Barrier struct { type Barrier struct {
NodeName string NodeName string
IPAddress string IPAddress string
Mode string Mode string
Process *exec.Cmd Process *exec.Cmd
} }
// CreateBarrierSession Command to run "barrier.barrierc --debug INFO -f 192.168.0.175" // CreateBarrierSession Command to run "barrier.barrierc --debug INFO -f 192.168.0.175"
func (b *Barrier)CreateBarrierSession() error { func (b *Barrier) CreateBarrierSession() error {
//Checks if barrier client exists //Checks if barrier client exists
if err := DetectBarrier(); err != nil { if err := DetectBarrier(); err != nil {
@@ -31,7 +31,7 @@ func (b *Barrier)CreateBarrierSession() error {
return err return err
} }
cmd := exec.Command("sudo","-u",configResp.SystemUsername,"barrierc","-f","--debug", "DEBUG" ,"--log", "/tmp/barrier.log",b.IPAddress) cmd := exec.Command("sudo", "-u", configResp.SystemUsername, "barrierc", "-f", "--debug", "DEBUG", "--log", "/tmp/barrier.log", b.IPAddress)
// USE THE FOLLOWING TO DEBUG // USE THE FOLLOWING TO DEBUG
//cmdReader, err := cmd.StdoutPipe() //cmdReader, err := cmd.StdoutPipe()
@@ -62,18 +62,17 @@ func (b *Barrier)CreateBarrierSession() error {
println(cmd.Path) println(cmd.Path)
// Saves the state of the command in the struct // Saves the state of the command in the struct
b.Process = cmd b.Process = cmd
return nil return nil
} }
// DeleteBarrierSession Deletes barrier client session running // DeleteBarrierSession Deletes barrier client session running
func (b *Barrier)DeleteBarrierSession() error { func (b *Barrier) DeleteBarrierSession() error {
// Halts the process // Halts the process
cmd := exec.Command("pkill" ,"barrierc") cmd := exec.Command("pkill", "barrierc")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return err return err
} }
return nil return nil
@@ -86,4 +85,4 @@ func DetectBarrier() error {
return err return err
} }
return nil return nil
} }

View File

@@ -1,497 +1,497 @@
package core package core
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
) )
var ( var (
left = [...]string{ left = [...]string{
"admiring", "admiring",
"adoring", "adoring",
"affectionate", "affectionate",
"agitated", "agitated",
"amazing", "amazing",
"angry", "angry",
"awesome", "awesome",
"beautiful", "beautiful",
"blissful", "blissful",
"bold", "bold",
"boring", "boring",
"brave", "brave",
"busy", "busy",
"charming", "charming",
"clever", "clever",
"cool", "cool",
"compassionate", "compassionate",
"competent", "competent",
"condescending", "condescending",
"confident", "confident",
"cranky", "cranky",
"crazy", "crazy",
"dazzling", "dazzling",
"determined", "determined",
"distracted", "distracted",
"dreamy", "dreamy",
"eager", "eager",
"ecstatic", "ecstatic",
"elastic", "elastic",
"elated", "elated",
"elegant", "elegant",
"eloquent", "eloquent",
"epic", "epic",
"exciting", "exciting",
"fervent", "fervent",
"festive", "festive",
"flamboyant", "flamboyant",
"focused", "focused",
"friendly", "friendly",
"frosty", "frosty",
"funny", "funny",
"gallant", "gallant",
"gifted", "gifted",
"goofy", "goofy",
"gracious", "gracious",
"great", "great",
"happy", "happy",
"hardcore", "hardcore",
"heuristic", "heuristic",
"hopeful", "hopeful",
"hungry", "hungry",
"infallible", "infallible",
"inspiring", "inspiring",
"interesting", "interesting",
"intelligent", "intelligent",
"jolly", "jolly",
"jovial", "jovial",
"keen", "keen",
"kind", "kind",
"laughing", "laughing",
"loving", "loving",
"lucid", "lucid",
"magical", "magical",
"mystifying", "mystifying",
"modest", "modest",
"musing", "musing",
"naughty", "naughty",
"nervous", "nervous",
"nice", "nice",
"nifty", "nifty",
"nostalgic", "nostalgic",
"objective", "objective",
"optimistic", "optimistic",
"peaceful", "peaceful",
"pedantic", "pedantic",
"pensive", "pensive",
"practical", "practical",
"priceless", "priceless",
"quirky", "quirky",
"quizzical", "quizzical",
"recursing", "recursing",
"relaxed", "relaxed",
"reverent", "reverent",
"romantic", "romantic",
"sad", "sad",
"serene", "serene",
"sharp", "sharp",
"silly", "silly",
"sleepy", "sleepy",
"stoic", "stoic",
"strange", "strange",
"stupefied", "stupefied",
"suspicious", "suspicious",
"sweet", "sweet",
"tender", "tender",
"thirsty", "thirsty",
"trusting", "trusting",
"unruffled", "unruffled",
"upbeat", "upbeat",
"vibrant", "vibrant",
"vigilant", "vigilant",
"vigorous", "vigorous",
"wizardly", "wizardly",
"wonderful", "wonderful",
"xenodochial", "xenodochial",
"youthful", "youthful",
"zealous", "zealous",
"zen", "zen",
} }
// Docker, starting from 0.7.x, generates names from notable scientists and hackers. // Docker, starting from 0.7.x, generates names from notable scientists and hackers.
// Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa. // Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa.
right = [...]string{ right = [...]string{
"aardvark", "aardvark",
"abyssinian", "abyssinian",
"affenpinscher", "affenpinscher",
"akbash", "akbash",
"akita", "akita",
"albatross", "albatross",
"alligator", "alligator",
"alpaca", "alpaca",
"angelfish", "angelfish",
"ant", "ant",
"anteater", "anteater",
"antelope", "antelope",
"ape", "ape",
"armadillo", "armadillo",
"ass", "ass",
"avocet", "avocet",
"axolotl", "axolotl",
"baboon", "baboon",
"badger", "badger",
"balinese", "balinese",
"bandicoot", "bandicoot",
"barb", "barb",
"barnacle", "barnacle",
"barracuda", "barracuda",
"bat", "bat",
"beagle", "beagle",
"bear", "bear",
"beaver", "beaver",
"bee", "bee",
"beetle", "beetle",
"binturong", "binturong",
"bird", "bird",
"birman", "birman",
"bison", "bison",
"bloodhound", "bloodhound",
"boar", "boar",
"bobcat", "bobcat",
"bombay", "bombay",
"bongo", "bongo",
"bonobo", "bonobo",
"booby", "booby",
"budgerigar", "budgerigar",
"buffalo", "buffalo",
"bulldog", "bulldog",
"bullfrog", "bullfrog",
"burmese", "burmese",
"butterfly", "butterfly",
"caiman", "caiman",
"camel", "camel",
"capybara", "capybara",
"caracal", "caracal",
"caribou", "caribou",
"cassowary", "cassowary",
"cat", "cat",
"caterpillar", "caterpillar",
"catfish", "catfish",
"cattle", "cattle",
"centipede", "centipede",
"chameleon", "chameleon",
"chamois", "chamois",
"cheetah", "cheetah",
"chicken", "chicken",
"chihuahua", "chihuahua",
"chimpanzee", "chimpanzee",
"chinchilla", "chinchilla",
"chinook", "chinook",
"chipmunk", "chipmunk",
"chough", "chough",
"cichlid", "cichlid",
"clam", "clam",
"coati", "coati",
"cobra", "cobra",
"cockroach", "cockroach",
"cod", "cod",
"collie", "collie",
"coral", "coral",
"cormorant", "cormorant",
"cougar", "cougar",
"cow", "cow",
"coyote", "coyote",
"crab", "crab",
"crane", "crane",
"crocodile", "crocodile",
"crow", "crow",
"curlew", "curlew",
"cuscus", "cuscus",
"cuttlefish", "cuttlefish",
"dachshund", "dachshund",
"dalmatian", "dalmatian",
"deer", "deer",
"dhole", "dhole",
"dingo", "dingo",
"dinosaur", "dinosaur",
"discus", "discus",
"dodo", "dodo",
"dog", "dog",
"dogfish", "dogfish",
"dolphin", "dolphin",
"donkey", "donkey",
"dormouse", "dormouse",
"dotterel", "dotterel",
"dove", "dove",
"dragonfly", "dragonfly",
"drever", "drever",
"duck", "duck",
"dugong", "dugong",
"dunker", "dunker",
"dunlin", "dunlin",
"eagle", "eagle",
"earwig", "earwig",
"echidna", "echidna",
"eel", "eel",
"eland", "eland",
"elephant", "elephant",
"elk", "elk",
"emu", "emu",
"falcon", "falcon",
"ferret", "ferret",
"finch", "finch",
"fish", "fish",
"flamingo", "flamingo",
"flounder", "flounder",
"fly", "fly",
"fossa", "fossa",
"fox", "fox",
"frigatebird", "frigatebird",
"frog", "frog",
"galago", "galago",
"gar", "gar",
"gaur", "gaur",
"gazelle", "gazelle",
"gecko", "gecko",
"gerbil", "gerbil",
"gharial", "gharial",
"gibbon", "gibbon",
"giraffe", "giraffe",
"gnat", "gnat",
"gnu", "gnu",
"goat", "goat",
"goldfinch", "goldfinch",
"goldfish", "goldfish",
"goose", "goose",
"gopher", "gopher",
"gorilla", "gorilla",
"goshawk", "goshawk",
"grasshopper", "grasshopper",
"greyhound", "greyhound",
"grouse", "grouse",
"guanaco", "guanaco",
"gull", "gull",
"guppy", "guppy",
"hamster", "hamster",
"hare", "hare",
"harrier", "harrier",
"havanese", "havanese",
"hawk", "hawk",
"hedgehog", "hedgehog",
"heron", "heron",
"herring", "herring",
"himalayan", "himalayan",
"hippopotamus", "hippopotamus",
"hornet", "hornet",
"horse", "horse",
"human", "human",
"hummingbird", "hummingbird",
"hyena", "hyena",
"ibis", "ibis",
"iguana", "iguana",
"impala", "impala",
"indri", "indri",
"insect", "insect",
"jackal", "jackal",
"jaguar", "jaguar",
"javanese", "javanese",
"jay", "jay",
"jellyfish", "jellyfish",
"kakapo", "kakapo",
"kangaroo", "kangaroo",
"kingfisher", "kingfisher",
"kiwi", "kiwi",
"koala", "koala",
"kouprey", "kouprey",
"kudu", "kudu",
"labradoodle", "labradoodle",
"ladybird", "ladybird",
"lapwing", "lapwing",
"lark", "lark",
"lemming", "lemming",
"lemur", "lemur",
"leopard", "leopard",
"liger", "liger",
"lion", "lion",
"lionfish", "lionfish",
"lizard", "lizard",
"llama", "llama",
"lobster", "lobster",
"locust", "locust",
"loris", "loris",
"louse", "louse",
"lynx", "lynx",
"lyrebird", "lyrebird",
"macaw", "macaw",
"magpie", "magpie",
"mallard", "mallard",
"maltese", "maltese",
"manatee", "manatee",
"mandrill", "mandrill",
"markhor", "markhor",
"marten", "marten",
"mastiff", "mastiff",
"mayfly", "mayfly",
"meerkat", "meerkat",
"millipede", "millipede",
"mink", "mink",
"mole", "mole",
"molly", "molly",
"mongoose", "mongoose",
"mongrel", "mongrel",
"monkey", "monkey",
"moorhen", "moorhen",
"moose", "moose",
"mosquito", "mosquito",
"moth", "moth",
"mouse", "mouse",
"mule", "mule",
"narwhal", "narwhal",
"neanderthal", "neanderthal",
"newfoundland", "newfoundland",
"newt", "newt",
"nightingale", "nightingale",
"numbat", "numbat",
"ocelot", "ocelot",
"octopus", "octopus",
"okapi", "okapi",
"olm", "olm",
"opossum", "opossum",
"orangutan", "orangutan",
"oryx", "oryx",
"ostrich", "ostrich",
"otter", "otter",
"owl", "owl",
"ox", "ox",
"oyster", "oyster",
"pademelon", "pademelon",
"panther", "panther",
"parrot", "parrot",
"partridge", "partridge",
"peacock", "peacock",
"peafowl", "peafowl",
"pekingese", "pekingese",
"pelican", "pelican",
"penguin", "penguin",
"persian", "persian",
"pheasant", "pheasant",
"pig", "pig",
"pigeon", "pigeon",
"pika", "pika",
"pike", "pike",
"piranha", "piranha",
"platypus", "platypus",
"pointer", "pointer",
"pony", "pony",
"poodle", "poodle",
"porcupine", "porcupine",
"porpoise", "porpoise",
"possum", "possum",
"prawn", "prawn",
"puffin", "puffin",
"pug", "pug",
"puma", "puma",
"quail", "quail",
"quelea", "quelea",
"quetzal", "quetzal",
"quokka", "quokka",
"quoll", "quoll",
"rabbit", "rabbit",
"raccoon", "raccoon",
"ragdoll", "ragdoll",
"rail", "rail",
"ram", "ram",
"rat", "rat",
"rattlesnake", "rattlesnake",
"raven", "raven",
"reindeer", "reindeer",
"rhinoceros", "rhinoceros",
"robin", "robin",
"rook", "rook",
"rottweiler", "rottweiler",
"ruff", "ruff",
"salamander", "salamander",
"salmon", "salmon",
"sandpiper", "sandpiper",
"saola", "saola",
"sardine", "sardine",
"scorpion", "scorpion",
"seahorse", "seahorse",
"seal", "seal",
"serval", "serval",
"shark", "shark",
"sheep", "sheep",
"shrew", "shrew",
"shrimp", "shrimp",
"siamese", "siamese",
"siberian", "siberian",
"skunk", "skunk",
"sloth", "sloth",
"snail", "snail",
"snake", "snake",
"snowshoe", "snowshoe",
"somali", "somali",
"sparrow", "sparrow",
"spider", "spider",
"sponge", "sponge",
"squid", "squid",
"squirrel", "squirrel",
"starfish", "starfish",
"starling", "starling",
"stingray", "stingray",
"stinkbug", "stinkbug",
"stoat", "stoat",
"stork", "stork",
"swallow", "swallow",
"swan", "swan",
"tang", "tang",
"tapir", "tapir",
"tarsier", "tarsier",
"termite", "termite",
"tetra", "tetra",
"tiffany", "tiffany",
"tiger", "tiger",
"toad", "toad",
"tortoise", "tortoise",
"toucan", "toucan",
"tropicbird", "tropicbird",
"trout", "trout",
"tuatara", "tuatara",
"turkey", "turkey",
"turtle", "turtle",
"uakari", "uakari",
"uguisu", "uguisu",
"umbrellabird", "umbrellabird",
"viper", "viper",
"vulture", "vulture",
"wallaby", "wallaby",
"walrus", "walrus",
"warthog", "warthog",
"wasp", "wasp",
"weasel", "weasel",
"whale", "whale",
"whippet", "whippet",
"wildebeest", "wildebeest",
"wolf", "wolf",
"wolverine", "wolverine",
"wombat", "wombat",
"woodcock", "woodcock",
"woodlouse", "woodlouse",
"woodpecker", "woodpecker",
"worm", "worm",
"wrasse", "wrasse",
"wren", "wren",
"yak", "yak",
"zebra", "zebra",
"zebu", "zebu",
"zonkey", "zonkey",
"zorse", "zorse",
} }
) )
func GetRandomName(retry int) string { func GetRandomName(retry int) string {
name := fmt.Sprintf("%s_%s_%s", left[rand.Intn(len(left))], left[rand.Intn(len(left))], right[rand.Intn(len(right))]) name := fmt.Sprintf("%s_%s_%s", left[rand.Intn(len(left))], left[rand.Intn(len(left))], right[rand.Intn(len(right))])
if retry > 0 { if retry > 0 {
name = fmt.Sprintf("%s_%d", name, rand.Intn(10)) name = fmt.Sprintf("%s_%d", name, rand.Intn(10))
} }
return name return name
} }

View File

@@ -1,140 +1,139 @@
package core package core
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gorilla/websocket" "github.com/Akilan1999/remotegameplay/config"
"io/ioutil" "github.com/gorilla/websocket"
"laplace/config" "io/ioutil"
"os" "os"
) )
type Room struct { type Room struct {
ID string `json:"id"` ID string `json:"id"`
Sessions map[string]*StreamSession Sessions map[string]*StreamSession
CallerConn *websocket.Conn CallerConn *websocket.Conn
BarrierSession *Barrier BarrierSession *Barrier
} }
type StreamSession struct { type StreamSession struct {
ID string ID string
Offer string Offer string
Answer string Answer string
CallerIceCandidates []string CallerIceCandidates []string
CalleeIceCandidates []string CalleeIceCandidates []string
CallerConn *websocket.Conn CallerConn *websocket.Conn
CalleeConn *websocket.Conn CalleeConn *websocket.Conn
} }
var roomMap = make(map[string]*Room) var roomMap = make(map[string]*Room)
func GetRoom(id string) *Room { func GetRoom(id string) *Room {
return roomMap[id] return roomMap[id]
} }
func NewRoom(callerConn *websocket.Conn) *Room { func NewRoom(callerConn *websocket.Conn) *Room {
room := Room{ room := Room{
ID: newRoomID(), ID: newRoomID(),
Sessions: make(map[string]*StreamSession), Sessions: make(map[string]*StreamSession),
CallerConn: callerConn, CallerConn: callerConn,
} }
roomMap[room.ID] = &room roomMap[room.ID] = &room
err := room.writeToFile() err := room.writeToFile()
if err != nil { if err != nil {
println(err) println(err)
} }
return &room return &room
} }
func newRoomID() string { func newRoomID() string {
id := GetRandomName(0) id := GetRandomName(0)
for GetRoom(id) != nil { for GetRoom(id) != nil {
id = GetRandomName(0) id = GetRandomName(0)
} }
return id return id
} }
func RemoveRoom(id string) { func RemoveRoom(id string) {
roomMap[id] = nil roomMap[id] = nil
// Writes nil to the file // Writes nil to the file
roomMap[id].writeToFile() roomMap[id].writeToFile()
} }
func (room *Room) GetSession(id string) *StreamSession { func (room *Room) GetSession(id string) *StreamSession {
return room.Sessions[id] return room.Sessions[id]
} }
func (room *Room) NewSession(calleeConn *websocket.Conn) *StreamSession { func (room *Room) NewSession(calleeConn *websocket.Conn) *StreamSession {
session := StreamSession{ session := StreamSession{
ID: room.newSessionID(), ID: room.newSessionID(),
CallerIceCandidates: []string{}, CallerIceCandidates: []string{},
CalleeIceCandidates: []string{}, CalleeIceCandidates: []string{},
CallerConn: room.CallerConn, CallerConn: room.CallerConn,
CalleeConn: calleeConn, CalleeConn: calleeConn,
} }
room.Sessions[session.ID] = &session room.Sessions[session.ID] = &session
return &session return &session
} }
func (room *Room) newSessionID() string { func (room *Room) newSessionID() string {
id := fmt.Sprintf("%s$%s", room.ID, GetRandomName(0)) id := fmt.Sprintf("%s$%s", room.ID, GetRandomName(0))
for GetRoom(id) != nil { for GetRoom(id) != nil {
id = fmt.Sprintf("%s$%s", room.ID, GetRandomName(0)) id = fmt.Sprintf("%s$%s", room.ID, GetRandomName(0))
} }
return id return id
} }
// Write to rooms.json file // Write to rooms.json file
func (room *Room)writeToFile() error { func (room *Room) writeToFile() error {
file, err := json.MarshalIndent(room, "", " ") file, err := json.MarshalIndent(room, "", " ")
if err != nil { if err != nil {
return err return err
} }
// Get Path from config // Get Path from config
config, err := config.ConfigInit() config, err := config.ConfigInit()
if err != nil { if err != nil {
return err return err
} }
// Write room struct to json file // Write room struct to json file
err = ioutil.WriteFile(config.Rooms, file, 0644) err = ioutil.WriteFile(config.Rooms, file, 0644)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
// ReadRoomsFile Reads rooms file and return struct room id // ReadRoomsFile Reads rooms file and return struct room id
func ReadRoomsFile() (*Room, error) { func ReadRoomsFile() (*Room, error) {
config, err := config.ConfigInit() config, err := config.ConfigInit()
if err != nil { if err != nil {
return nil,err return nil, err
} }
jsonFile, err := os.Open(config.Rooms) jsonFile, err := os.Open(config.Rooms)
// if we os.Open returns an error then handle it // if we os.Open returns an error then handle it
if err != nil { if err != nil {
return nil,err return nil, err
} }
// defer the closing of our jsonFile so that we can parse it later on
defer jsonFile.Close()
// defer the closing of our jsonFile so that we can parse it later on // read our opened xmlFile as a byte array.
defer jsonFile.Close() byteValue, _ := ioutil.ReadAll(jsonFile)
// read our opened xmlFile as a byte array. // we initialize our Users array
byteValue, _ := ioutil.ReadAll(jsonFile) var rooms *Room
// we initialize our Users array // we unmarshal our byteArray which contains our
var rooms *Room // jsonFile's content into 'users' which we defined above
json.Unmarshal(byteValue, &rooms)
// we unmarshal our byteArray which contains our return rooms, nil
// jsonFile's content into 'users' which we defined above
json.Unmarshal(byteValue, &rooms)
return rooms, nil
} }

View File

@@ -1,210 +1,210 @@
package core package core
import ( import (
"fmt" "fmt"
"github.com/gorilla/websocket" "github.com/Akilan1999/remotegameplay/config"
"laplace/config" "github.com/gorilla/websocket"
"log" "log"
"net/http" "net/http"
"time" "time"
) )
type WSMessage struct { type WSMessage struct {
SessionID string SessionID string
Type string Type string
Value string Value string
} }
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
ReadBufferSize: 1024, ReadBufferSize: 1024,
WriteBufferSize: 1024, WriteBufferSize: 1024,
} }
func sendHeartBeatWS(ticker *time.Ticker, conn *websocket.Conn, quit chan struct{}) { func sendHeartBeatWS(ticker *time.Ticker, conn *websocket.Conn, quit chan struct{}) {
for { for {
select { select {
case <- ticker.C: case <-ticker.C:
_ = conn.WriteJSON(WSMessage{ _ = conn.WriteJSON(WSMessage{
Type: "beat", Type: "beat",
}) })
case <- quit: case <-quit:
log.Println("heartbeat stopped") log.Println("heartbeat stopped")
return return
} }
} }
} }
func GetHttp() *http.ServeMux { func GetHttp() *http.ServeMux {
server := http.NewServeMux() server := http.NewServeMux()
server.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("files/static")))) server.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("files/static"))))
server.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { server.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "files/main.html") http.ServeFile(w, r, "files/main.html")
}) })
//Get hostname of the current machine //Get hostname of the current machine
server.HandleFunc("/hostname", func(w http.ResponseWriter, r *http.Request) { server.HandleFunc("/hostname", func(w http.ResponseWriter, r *http.Request) {
// Read hostname from config file // Read hostname from config file
configResp, err := config.ConfigInit() configResp, err := config.ConfigInit()
if err != nil { if err != nil {
print(err) print(err)
return return
} }
fmt.Fprintf(w, configResp.SystemUsername) fmt.Fprintf(w, configResp.SystemUsername)
}) })
//Get SSH password of the current machine //Get SSH password of the current machine
server.HandleFunc("/SSHPassword", func(w http.ResponseWriter, r *http.Request) { server.HandleFunc("/SSHPassword", func(w http.ResponseWriter, r *http.Request) {
// Read hostname from config file // Read hostname from config file
configResp, err := config.ConfigInit() configResp, err := config.ConfigInit()
if err != nil { if err != nil {
print(err) print(err)
return return
} }
fmt.Fprintf(w, configResp.SSHPassword) fmt.Fprintf(w, configResp.SSHPassword)
}) })
server.HandleFunc("/ws_serve", func(writer http.ResponseWriter, request *http.Request) { server.HandleFunc("/ws_serve", func(writer http.ResponseWriter, request *http.Request) {
conn, _ := upgrader.Upgrade(writer, request, nil) conn, _ := upgrader.Upgrade(writer, request, nil)
room := NewRoom(conn) room := NewRoom(conn)
if err := conn.WriteJSON(WSMessage{ if err := conn.WriteJSON(WSMessage{
SessionID: "", SessionID: "",
Type: "newRoom", Type: "newRoom",
Value: room.ID, Value: room.ID,
}); err != nil { }); err != nil {
log.Println("newSessionWriteJsonError.", err) log.Println("newSessionWriteJsonError.", err)
return return
} }
go func(r *Room) { go func(r *Room) {
ticker := time.NewTicker(10 * time.Second) ticker := time.NewTicker(10 * time.Second)
quit := make(chan struct{}) quit := make(chan struct{})
defer func() { defer func() {
ticker.Stop() ticker.Stop()
_ = room.CallerConn.Close() _ = room.CallerConn.Close()
close(quit) close(quit)
RemoveRoom(r.ID) RemoveRoom(r.ID)
// Close the barrier session // Close the barrier session
if r.BarrierSession != nil { if r.BarrierSession != nil {
r.BarrierSession.DeleteBarrierSession() r.BarrierSession.DeleteBarrierSession()
} }
for sID, s := range r.Sessions { for sID, s := range r.Sessions {
_ = s.CalleeConn.WriteJSON(WSMessage{ _ = s.CalleeConn.WriteJSON(WSMessage{
Type: "roomClosed", Type: "roomClosed",
SessionID: sID, SessionID: sID,
}) })
} }
}() }()
go sendHeartBeatWS(ticker, conn, quit) go sendHeartBeatWS(ticker, conn, quit)
//noinspection ALL //noinspection ALL
defer room.CallerConn.Close() defer room.CallerConn.Close()
for { for {
var msg WSMessage var msg WSMessage
if err := room.CallerConn.ReadJSON(&msg); err != nil { if err := room.CallerConn.ReadJSON(&msg); err != nil {
log.Println("websocketError.", err) log.Println("websocketError.", err)
return return
} }
//log.Println(msg) //log.Println(msg)
s := room.GetSession(msg.SessionID) s := room.GetSession(msg.SessionID)
if s == nil { if s == nil {
log.Println("session nil.", msg.SessionID) log.Println("session nil.", msg.SessionID)
} }
if msg.Type == "addCallerIceCandidate" { if msg.Type == "addCallerIceCandidate" {
s.CallerIceCandidates = append(s.CallerIceCandidates, msg.Value) s.CallerIceCandidates = append(s.CallerIceCandidates, msg.Value)
} else if msg.Type == "gotOffer" { } else if msg.Type == "gotOffer" {
s.Offer = msg.Value s.Offer = msg.Value
} }
if err := s.CalleeConn.WriteJSON(msg); err != nil { if err := s.CalleeConn.WriteJSON(msg); err != nil {
log.Println("serveEchoWriteJsonError.", err) log.Println("serveEchoWriteJsonError.", err)
} }
} }
}(room) }(room)
}) })
server.HandleFunc("/ws_connect", func(writer http.ResponseWriter, request *http.Request) { server.HandleFunc("/ws_connect", func(writer http.ResponseWriter, request *http.Request) {
conn, _ := upgrader.Upgrade(writer, request, nil) conn, _ := upgrader.Upgrade(writer, request, nil)
ids, ok := request.URL.Query()["id"] ids, ok := request.URL.Query()["id"]
if !ok || ids[0] == "" { if !ok || ids[0] == "" {
return return
} }
room := GetRoom(ids[0]) room := GetRoom(ids[0])
ip, ok := request.URL.Query()["barrierip"] ip, ok := request.URL.Query()["barrierip"]
if ok && ip[0] != "" { if ok && ip[0] != "" {
//Declaring struct //Declaring struct
var barriersession Barrier var barriersession Barrier
barriersession.IPAddress = ip[0] barriersession.IPAddress = ip[0]
room.BarrierSession = &barriersession room.BarrierSession = &barriersession
// Creates a barrier session // Creates a barrier session
err := room.BarrierSession.CreateBarrierSession() err := room.BarrierSession.CreateBarrierSession()
if err != nil { if err != nil {
return return
} }
} }
if room == nil { if room == nil {
_ = conn.WriteJSON(WSMessage{ _ = conn.WriteJSON(WSMessage{
Type: "roomNotFound", Type: "roomNotFound",
}) })
return return
} }
session := room.NewSession(conn) session := room.NewSession(conn)
if err := room.CallerConn.WriteJSON(WSMessage{ if err := room.CallerConn.WriteJSON(WSMessage{
SessionID: session.ID, SessionID: session.ID,
Type: "newSession", Type: "newSession",
Value: session.ID, Value: session.ID,
}); err != nil { }); err != nil {
log.Println("callerWriteJsonError.", err) log.Println("callerWriteJsonError.", err)
return return
} }
if err := conn.WriteJSON(WSMessage{ if err := conn.WriteJSON(WSMessage{
SessionID: session.ID, SessionID: session.ID,
Type: "newSession", Type: "newSession",
Value: session.ID, Value: session.ID,
}); err != nil { }); err != nil {
log.Println("calleeWriteJsonError.", err) log.Println("calleeWriteJsonError.", err)
return return
} }
go func(s *StreamSession) { go func(s *StreamSession) {
//noinspection ALL //noinspection ALL
defer s.CalleeConn.Close() defer s.CalleeConn.Close()
for { for {
var msg WSMessage var msg WSMessage
if err := conn.ReadJSON(&msg); err != nil { if err := conn.ReadJSON(&msg); err != nil {
log.Println("websocketError.", err) log.Println("websocketError.", err)
return return
} }
//log.Println(msg) //log.Println(msg)
if msg.SessionID == s.ID { if msg.SessionID == s.ID {
if msg.Type == "addCalleeIceCandidate" { if msg.Type == "addCalleeIceCandidate" {
s.CalleeIceCandidates = append(s.CalleeIceCandidates, msg.Value) s.CalleeIceCandidates = append(s.CalleeIceCandidates, msg.Value)
} else if msg.Type == "gotAnswer" { } else if msg.Type == "gotAnswer" {
s.Answer = msg.Value s.Answer = msg.Value
} }
if err := s.CallerConn.WriteJSON(msg); err != nil { if err := s.CallerConn.WriteJSON(msg); err != nil {
log.Println("connectEchoWriteJsonError.", err) log.Println("connectEchoWriteJsonError.", err)
} }
} }
} }
}(session) }(session)
}) })
return server return server
} }

View File

@@ -3,19 +3,20 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Laplace</title> <title>RemoteGamePlay</title>
<link rel="stylesheet" href="/static/bootstrap.min.css"> <link rel="stylesheet" href="/static/bootstrap.min.css">
<link rel="stylesheet" href="/static/main.css?v=0.0.5"> <link rel="stylesheet" href="/static/main.css?v=0.0.5">
</head> </head>
<body> <body>
<nav class="navbar"> <nav class="navbar">
<a class="navbar-brand text-dark" href="/">Laplace</a> <a class="navbar-brand text-dark" href="/">RemoteGamePlay</a>
<span class="navbar-brand header-room" id="room-text"></span> <span class="navbar-brand header-room" id="room-text"></span>
</nav> </nav>
<div class="container"> <div class="container">
<div class="panel" id="panel"> <div class="panel" id="panel">
<p> based on the open source project Laplace </p>
<p class="help-text"> <p class="help-text">
<b>Laplace</b> is an open-source project to enable screen sharing directly via browser. <b>Laplace</b> is an open-source project to enable screen sharing directly via browser.
Made possible using WebRTC for low latency peer-to-peer connections, Made possible using WebRTC for low latency peer-to-peer connections,
@@ -45,18 +46,18 @@
<div class="form-group"> <div class="form-group">
<input id="inputRoomID" type="text" class="form-control form-control-lg text-center" placeholder="Enter room ID" required> <input id="inputRoomID" type="text" class="form-control form-control-lg text-center" placeholder="Enter room ID" required>
</div> </div>
<!-- <h4>Connect to Barrier KVM (Beta)</h4>--> <h4>Connect to Barrier KVM (Beta)</h4>
<!-- <p class="help-text">--> <p class="help-text">
<!-- This is a feature is still on beta testing. This feature will ensure that you can share the keyboard--> This is a feature is still on beta testing. This feature will ensure that you can share the keyboard
<!-- and mouse of the server machine.--> and mouse of the server machine.
<!-- <br>--> <br>
<!-- <span class="text-success font-weight-bold"> Note: This only works if the laplace server has barrierc installed and the room name you are connecting--> <span class="text-success font-weight-bold"> Note: This only works if the laplace server has Barrierc installed and the room name you are connecting
<!-- too belongs to the server.</span>--> too belongs to the server.</span>
<!-- </p>--> </p>
<!-- <h6>Host Name: <span id="hostname"> Not detected </span></h6>--> <h6>Host Name: <span id="hostname"> Not detected </span></h6>
<!-- <div class="form-group">--> <div class="form-group">
<!-- <input id="barrierIP" type="text" class="form-control form-control-lg text-center" placeholder="IPV6 or IPV4 address">--> <input id="barrierIP" type="text" class="form-control form-control-lg text-center" placeholder="IPV6 or IPV4 address">
<!-- </div>--> </div>
<button type="submit" class="btn btn-dark btn-block" value="submit">Join</button> <button type="submit" class="btn btn-dark btn-block" value="submit">Join</button>
</form> </form>
</div> </div>
@@ -181,8 +182,8 @@
<div class="container"> <div class="container">
<hr> <hr>
<div class="text-center small" id="footer"> <div class="text-center small" id="footer">
<span>Laplace Project by Adam Jordan.</span> <span><a href="https://github.com/Akilan1999/remotegameplay">RemoteGamePlay</a> Project by <a href="https://akilan.io">Akilan Selvacoumar</a> based on the fork
<a href="https://github.com/adamyordan/laplace">Source Code</a> of <a href="https://github.com/Akilan1999/laplace/tree/keyboard_mouse">Laplace</a>.</span>
</div> </div>
</div> </div>

2
go.mod
View File

@@ -1,4 +1,4 @@
module laplace module github.com/Akilan1999/remotegameplay
go 1.16 go 1.16

19
main.go
View File

@@ -4,8 +4,8 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"laplace/config" "github.com/Akilan1999/remotegameplay/config"
"laplace/core" "github.com/Akilan1999/remotegameplay/core"
"log" "log"
"math/rand" "math/rand"
"net/http" "net/http"
@@ -15,6 +15,7 @@ import (
func main() { func main() {
addr := flag.String("addr", "0.0.0.0:443", "Listen address") addr := flag.String("addr", "0.0.0.0:443", "Listen address")
port := flag.String("port", "8888", "port for running the server")
tls := flag.Bool("tls", false, "Use TLS") tls := flag.Bool("tls", false, "Use TLS")
setconfig := flag.Bool("setconfig", false, "Generates a config file") setconfig := flag.Bool("setconfig", false, "Generates a config file")
certFile := flag.String("certFile", "files/server.crt", "TLS cert file") certFile := flag.String("certFile", "files/server.crt", "TLS cert file")
@@ -23,7 +24,7 @@ func main() {
roomInfo := flag.Bool("roomInfo", false, "Getting room id of headless server") roomInfo := flag.Bool("roomInfo", false, "Getting room id of headless server")
killServer := flag.Bool("killServer", false, "Kills the laplace") killServer := flag.Bool("killServer", false, "Kills the laplace")
killChromium := flag.Bool("killChromium", false, "Kills all chromuim") killChromium := flag.Bool("killChromium", false, "Kills all chromuim")
BinaryToExcute := flag.String("BinaryToExecute","","Providing path (i.e Absolute path) of binary to execute") BinaryToExcute := flag.String("BinaryToExecute", "", "Providing path (i.e Absolute path) of binary to execute")
flag.Parse() flag.Parse()
@@ -47,7 +48,6 @@ func main() {
return return
} }
// Running in headless mode // Running in headless mode
if *headless { if *headless {
Config, err := config.ConfigInit() Config, err := config.ConfigInit()
@@ -75,7 +75,7 @@ func main() {
} }
// Starting screen share headless // Starting screen share headless
cmd := exec.Command("chromium-browser" ,"--no-sandbox","--auto-select-desktop-capture-source=Entire screen","--url","https://" + Addr + ":8888/?mode=headless","--ignore-certificate-errors") cmd := exec.Command("chromium-browser", "--no-sandbox", "--auto-select-desktop-capture-source=Entire screen", "--url", "https://"+Addr+":"+*port+"/?mode=headless", "--ignore-certificate-errors")
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
@@ -96,22 +96,21 @@ func main() {
// kills laplace server // kills laplace server
if *killServer { if *killServer {
cmd := exec.Command("pkill" ,"laplace") cmd := exec.Command("pkill", "remotegameplay")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
fmt.Println(err) fmt.Println(err)
} }
return return
} }
// kills chromium server // kills chromium server
if *killChromium { if *killChromium {
cmd := exec.Command("pkill" ,"chromium") cmd := exec.Command("pkill", "chromium")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
fmt.Println(err) fmt.Println(err)
} }
return return
} }
if *tls { if *tls {
log.Println("Listening on TLS:", *addr) log.Println("Listening on TLS:", *addr)
if err := http.ListenAndServeTLS(*addr, *certFile, *keyFile, server); err != nil { if err := http.ListenAndServeTLS(*addr, *certFile, *keyFile, server); err != nil {
@@ -155,7 +154,7 @@ func Ip4or6(s string) string {
func RunTask(task string) error { func RunTask(task string) error {
// Halts the process // Halts the process
cmd := exec.Command("sh",task) cmd := exec.Command("sh", task)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return err return err
} }

10
run.sh
View File

@@ -1,11 +1,11 @@
# Starts server and runs it silently (i.e no output in the terminal) # Starts server and runs it silently (i.e no output in the terminal)
./laplace -tls -addr 0.0.0.0:8888 > /dev/null 2>&1 & ./remotegameplay -tls -addr 0.0.0.0:${2} > /dev/null 2>&1 &
# Starts chromium browser and runs it silently (i.e no output in the terminal) # Starts chromium browser and runs it silently (i.e no output in the terminal)
./laplace -headless -addr ${1} > /dev/null 2>&1 & ./remotegameplay -headless -addr ${1} -port ${2} > /dev/null 2>&1 &
# Lets the script sleep for 2 seconds # Lets the script sleep for 2 seconds
sleep 2 sleep 2
# Gets roomInfo and stores in the tmp directory as JSON # Gets roomInfo and stores in the tmp directory as JSON
./laplace -headless -roomInfo > /tmp/output.txt ./remotegameplay -headless -roomInfo > /tmp/output.txt
# Gets the room ID from the JSON file and outputs to tmp directory as text # Gets the room ID from the JSON file and outputs to tmp directory as text
jq .id /tmp/output.txt > /tmp/test.txt jq .id /tmp/output.txt > /tmp/test.txt
# Gets roomID from tmp directory as text # Gets roomID from tmp directory as text
@@ -15,9 +15,9 @@ roomID=$(echo "$roomID" | tr -d '"')
# Checks if the IP address is a IPV6 or IPV4 address # Checks if the IP address is a IPV6 or IPV4 address
if [ "$1" != "${1#*[0-9].[0-9]}" ]; then if [ "$1" != "${1#*[0-9].[0-9]}" ]; then
echo "https://"${1}":8888/?roomID="${roomID} echo "https://"${1}":${2}/?roomID="${roomID}
elif [ "$1" != "${1#*:[0-9a-fA-F]}" ]; then elif [ "$1" != "${1#*:[0-9a-fA-F]}" ]; then
echo "https://["${1}"]:8888/?roomID="${roomID} echo "https://["${1}"]:${2}/?roomID="${roomID}
else else
echo "Unrecognized IP format '$1'" echo "Unrecognized IP format '$1'"
fi fi