diff --git a/Global.gd b/Global.gd
index 25eced2..67056ff 100644
--- a/Global.gd
+++ b/Global.gd
@@ -14,6 +14,7 @@ func AddInventoryItem(itemid, amount):
if(player_inventory_items[x].item_id == itemid):
print(str(player_inventory_items[x]))
player_inventory_items[x].amount += amount
+ Database.SaveInventory(player_inventory_items)
return
#if we reached here then no exisiting item is found and we iterate the array again
print("adding item")
@@ -24,6 +25,8 @@ func AddInventoryItem(itemid, amount):
player_inventory_items[x].shortdesc = "desc"
player_inventory_items[x].item_id = itemid
player_inventory_items[x].amount = amount
+ Database.SaveInventory(player_inventory_items)
+ return
func GoToScene(scene):
if current_scene != null:
diff --git a/Menu.tscn b/Menu.tscn
index a668ccd..09fa892 100644
--- a/Menu.tscn
+++ b/Menu.tscn
@@ -1,14 +1,16 @@
[gd_scene load_steps=7 format=2]
-[ext_resource path="res://Menu.gd" type="Script" id=1]
+[ext_resource path="res://MiscCodes/Menu.gd" type="Script" id=1]
[ext_resource path="res://pictures/animations/Loading/loading_ring.png" type="Texture" id=2]
[ext_resource path="res://MiscCodes/loading_ring.gd" type="Script" id=3]
-[ext_resource path="res://ring_of_races_font.ttf" type="DynamicFontData" id=4]
[ext_resource path="res://pictures/gui/backgrounds/treesbackground1.png" type="Texture" id=5]
+[sub_resource type="DynamicFontData" id=2]
+font_path = "res://ring_of_races_font.ttf"
+
[sub_resource type="DynamicFont" id=1]
size = 30
-font_data = ExtResource( 4 )
+font_data = SubResource( 2 )
[node name="Main Menu" type="Node2D"]
script = ExtResource( 1 )
diff --git a/MiscCodes/AnimationPlayer.gd b/MiscCodes/AnimationPlayer.gd
new file mode 100644
index 0000000..e69de29
diff --git a/MiscCodes/KinematicBody2D.gd b/MiscCodes/KinematicBody2D.gd
new file mode 100644
index 0000000..08dbaab
--- /dev/null
+++ b/MiscCodes/KinematicBody2D.gd
@@ -0,0 +1,71 @@
+extends KinematicBody2D
+
+const GRAVITY = 0.0
+const WALK_SPEED = 200
+const interaction_circle_size = 150
+onready var background_map = get_node("/root/Map1/background")
+onready var player = get_node("/root/Map1/Player")
+onready var cell_size = background_map._get_cell_size()
+onready var plants_map = get_node("/root/Map1/interaction_map")
+onready var interaction = get_node("/root/Map1/player_interaction")
+
+var velocity = Vector2()
+var world_position
+
+#Moving buttons
+func _physics_process(delta):
+ if Input.is_key_pressed(KEY_SPACE) or Input.is_mouse_button_pressed(BUTTON_LEFT):
+ _interaction_process()
+ velocity.y += delta * GRAVITY
+ if Input.is_action_pressed("move_left"):
+ velocity.x = -WALK_SPEED
+ elif Input.is_action_pressed("move_right"):
+ velocity.x = WALK_SPEED
+ elif Input.is_action_pressed("move_up"):
+ velocity.y = -WALK_SPEED
+ elif Input.is_action_pressed("move_down"):
+ velocity.y = WALK_SPEED
+ else:
+ velocity.x = 0
+ velocity.y = 0
+ move_and_slide(velocity, Vector2(0, -1))
+ Global.current_camera.Update()
+# if(interaction.get_cell(int(self.position.x / cell_size.x), int(self.position.y / cell_size.y)) == -1):
+# interaction.clear()
+# interaction.set_cell(int(self.position.x / cell_size.x), int(self.position.y / cell_size.y), 0)
+
+func InteractWithCell():
+ var plant_cell_mouse = plants_map.get_cell(int(world_position[0] / cell_size.x), int(world_position[1] / cell_size.y))
+ var plant_cell_character = plants_map.get_cell(int(self.position.x / cell_size.x), int(self.position.y / cell_size.y))
+
+ var background_cell = background_map.get_cell(int(world_position[0] / cell_size.x), int(world_position[1] / cell_size.y))
+ var interaction_cell = interaction.get_cell(int(world_position[0] / cell_size.x), int(world_position[1] / cell_size.y))
+ if plant_cell_mouse > 0 and plant_cell_mouse % 2 == 0:
+ Global.AddInventoryItem(3, 1)
+ plants_map.set_cell(int(world_position[0] / cell_size.x), int(world_position[1] / cell_size.y), (plant_cell_mouse-1))
+ AnimationOnInteraction(1)
+ elif plant_cell_character > 0 and plant_cell_character % 2 == 0:
+ Global.AddInventoryItem(3, 1)
+ plants_map.set_cell(int(self.position.x / cell_size.x), int(self.position.y / cell_size.y), (plant_cell_character-1))
+ AnimationOnInteraction(1)
+
+func _interaction_process():
+ if Input.is_key_pressed(KEY_SPACE) or Input.is_mouse_button_pressed(BUTTON_LEFT):
+ world_position = get_global_mouse_position()
+ InteractWithCell()
+
+func _input(event):
+ pass
+
+func AnimationOnInteraction(Item):
+ print("Item = ", Item, " Animation")
+ var itemimage = TextureRect.new()
+ itemimage.texture = load("res://pictures/inventory_iconpictures/food_items/herbs/saffron.png")
+ itemimage.set_position(Vector2(randf()*20-40, randf()*40-20))
+ add_child(itemimage)
+ yield(get_tree().create_timer(1.0), "timeout")
+ remove_child(itemimage)
+
+func _ready():
+ Global.player_inventory_items = Database.GetInventoryItems()
+
diff --git a/MiscCodes/Menu.gd b/MiscCodes/Menu.gd
new file mode 100644
index 0000000..41fc6a5
--- /dev/null
+++ b/MiscCodes/Menu.gd
@@ -0,0 +1,9 @@
+extends Node2D
+
+func _ready():
+ pass # Replace with function body.
+
+func _on_Btn_PlayGame_pressed():
+ Global.LoadSave()
+ Global.GoToScene("river_intersection_home_2")
+
diff --git a/MiscCodes/Menu_Buttons.gd b/MiscCodes/Menu_Buttons.gd
new file mode 100644
index 0000000..5e08f24
--- /dev/null
+++ b/MiscCodes/Menu_Buttons.gd
@@ -0,0 +1,16 @@
+extends Button
+
+
+# Declare member variables here. Examples:
+# var a = 2
+# var b = "text"
+
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+ pass # Replace with function body.
+
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+#func _process(delta):
+# pass
diff --git a/MiscCodes/Menu_PlayGame.gd b/MiscCodes/Menu_PlayGame.gd
new file mode 100644
index 0000000..bda25cc
--- /dev/null
+++ b/MiscCodes/Menu_PlayGame.gd
@@ -0,0 +1,27 @@
+extends Button
+
+
+# Declare member variables here. Examples:
+# var a = 2
+# var b = "text"
+
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+
+ pass # Replace with function body.
+
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+#func _process(delta):
+# pass
+
+
+
+func _on_Button_gui_input(event):
+ print(event)
+ if event == InputEventScreenTouch.CONNECT_ONESHOT:
+ get_tree().change_scene("res://river_intersection_home2.tscn")
+
+ # get_tree().change_scene("res://river_intersection_home2.tscn")
+ pass # Replace with function body.
diff --git a/MiscCodes/MiscButtons.gd b/MiscCodes/MiscButtons.gd
new file mode 100644
index 0000000..cc9a950
--- /dev/null
+++ b/MiscCodes/MiscButtons.gd
@@ -0,0 +1,14 @@
+extends TouchScreenButton
+
+#func _input(always):
+func _physics_process(delta):
+ if Input.is_action_pressed("move_left") and Input.is_action_pressed("move_right"):
+ show()
+ elif Input.is_action_pressed("ui_end"):
+ hide()
+
+
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+#func _process(delta):
+# pass
diff --git a/MiscCodes/Tilemap_CameraView.gd b/MiscCodes/Tilemap_CameraView.gd
new file mode 100644
index 0000000..7970bbb
--- /dev/null
+++ b/MiscCodes/Tilemap_CameraView.gd
@@ -0,0 +1,77 @@
+extends Camera2D
+
+
+onready var player = get_node("/root/Map1/Player")
+onready var background_map = get_node("/root/Map1/background")
+onready var screen_size = self.get_viewport_rect().size
+
+func _ready():
+ calculate_bounds()
+ Global.current_camera = self
+ $dev_statistics.visible = Global.dev_stats
+
+var once = true
+var lockedPlayerCamera = false
+var min_x = 0
+var min_y = 0
+var max_x = 0
+var max_y = 0
+var max_x_pixel = 0
+var max_y_pixel = 0
+
+#function that calculates the borders/bounds of the map
+func calculate_bounds():
+ var used_cells = background_map.get_used_cells()
+ for pos in used_cells:
+ if pos.x < min_x:
+ min_x = int(pos.x)
+ elif pos.x > max_x:
+ max_x = int(pos.x)
+ if pos.y < min_y:
+ min_y = int(pos.y)
+ elif pos.y > max_y:
+ max_y = int(pos.y)
+ print(min_x,"-",max_x, " AND " ,min_y , "-" , max_y)
+ max_x_pixel = (max_x * 32)
+ max_y_pixel = (max_y * 32)
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta):
+ if(Global.dev_stats):
+ $dev_statistics/fps_stats.text = "FPS: " + str(Performance.get_monitor(Performance.TIME_FPS))
+ CameraToPlayer()
+ if once:
+ once = false
+ #AnimateMoveCamera(player.position, Vector2(player.position.x - 100,player.position.y - 10), "position", 2)
+ pass
+
+func get_global_pos():
+ return Vector2(position.x, position.y)
+
+#Move camera to position
+func MoveCamera(x, y):
+ if x < int(screen_size.x / 2):
+ pass
+ else:
+ position.x = x
+ if y < int(screen_size.y / 2):
+ pass
+ else:
+ position.y = y
+
+func _on_Tween_tween_completed(object, key):
+ print(object, key)
+ lockedPlayerCamera = false
+
+func AnimateMoveCamera(source, destination, key, time):
+ var tween = get_node("/root/Map1/Tween")
+ lockedPlayerCamera = true
+ tween.interpolate_property(get_node("/root/Map1/Camera2D"), key, source, destination, time, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
+ tween.start()
+
+func CameraToPlayer():
+ if lockedPlayerCamera == false:
+ MoveCamera(player.position.x, player.position.y)
+
+func Update():
+ CameraToPlayer()
diff --git a/MiscCodes/TouchScreenButton.gd b/MiscCodes/TouchScreenButton.gd
new file mode 100644
index 0000000..05fcf91
--- /dev/null
+++ b/MiscCodes/TouchScreenButton.gd
@@ -0,0 +1,6 @@
+extends TouchScreenButton
+
+onready var ShowInventory = Global.ShowInventory
+
+func _input(always):
+ ShowInventory = 1;
diff --git a/MiscCodes/background_script.gd b/MiscCodes/background_script.gd
new file mode 100644
index 0000000..61eb943
--- /dev/null
+++ b/MiscCodes/background_script.gd
@@ -0,0 +1,20 @@
+extends TileMap
+
+onready var player = get_node("/root/Map1/Player")
+
+func _ready():
+ pass # Replace with function body.
+
+func _get_cell_size():
+ return cell_size
+
+func _unhandled_input(event):
+ var pl_pos = player.position
+ var pl_pos_tile = Vector2(pl_pos.x / cell_size.x, pl_pos.y / cell_size.y)
+ var pl_tile = get_cellv(pl_pos_tile)
+ if event == Input.action_press("map_interaction"):
+ if(pl_tile != -1):
+ set_cellv(pl_pos_tile, -1)
+
+func _on_Inventory_pressed():
+ Global.GoToScene("inventory_screen")
diff --git a/MiscScenes/Control.tscn b/MiscScenes/Control.tscn
new file mode 100644
index 0000000..f74fd5e
--- /dev/null
+++ b/MiscScenes/Control.tscn
@@ -0,0 +1,19 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://arrow_down.png" type="Texture" id=1]
+
+[sub_resource type="GradientTexture" id=1]
+
+[node name="Control" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="TouchScreenButton" type="TouchScreenButton" parent="."]
+position = Vector2( 890.674, 223.37 )
+scale = Vector2( 4.01662, 3.80615 )
+normal = ExtResource( 1 )
+pressed = SubResource( 1 )
+action = "ui_right"
diff --git a/Other/Planten.tres b/Other/Planten.tres
new file mode 100644
index 0000000..26989cb
--- /dev/null
+++ b/Other/Planten.tres
@@ -0,0 +1,3 @@
+[gd_resource type="TileSet" format=2]
+
+[resource]
diff --git a/Other/Planten.tsx b/Other/Planten.tsx
new file mode 100644
index 0000000..a4789ae
--- /dev/null
+++ b/Other/Planten.tsx
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Other/Planten.tsx.import b/Other/Planten.tsx.import
new file mode 100644
index 0000000..3d8c19f
--- /dev/null
+++ b/Other/Planten.tsx.import
@@ -0,0 +1,18 @@
+[remap]
+
+importer="vnen.tiled_tileset_importer"
+type="TileSet"
+valid=false
+
+[deps]
+
+source_file="res://Other/Planten.tsx"
+[params]
+
+custom_properties=true
+tile_metadata=false
+image_flags=7
+embed_internal_images=false
+save_tiled_properties=false
+apply_offset=false
+post_import_script=""
diff --git a/Other/Plants.tres b/Other/Plants.tres
new file mode 100644
index 0000000..841c0f3
--- /dev/null
+++ b/Other/Plants.tres
@@ -0,0 +1,182 @@
+[gd_resource type="TileSet" load_steps=2 format=2]
+
+[ext_resource path="res://pictures/tileset_images/Plants.png" type="Texture" id=1]
+
+
+
+[resource]
+0/name = "Plants.png 0"
+0/texture = ExtResource( 1 )
+0/tex_offset = Vector2( 0, 0 )
+0/modulate = Color( 1, 1, 1, 1 )
+0/region = Rect2( 32, 0, 32, 32 )
+0/tile_mode = 2
+0/autotile/icon_coordinate = Vector2( 0, 0 )
+0/autotile/tile_size = Vector2( 32, 32 )
+0/autotile/spacing = 0
+0/autotile/occluder_map = [ ]
+0/autotile/navpoly_map = [ ]
+0/autotile/priority_map = [ Vector3( 1, 0, 2 ) ]
+0/autotile/z_index_map = [ Vector3( 0, 0, 1 ), Vector3( 1, 0, 1 ), Vector3( 2, 0, 2 ) ]
+0/occluder_offset = Vector2( 0, 0 )
+0/navigation_offset = Vector2( 0, 0 )
+0/shape_offset = Vector2( 0, 0 )
+0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+0/shape_one_way = false
+0/shape_one_way_margin = 0.0
+0/shapes = [ ]
+0/z_index = 0
+1/name = "Plants.png 1"
+1/texture = ExtResource( 1 )
+1/tex_offset = Vector2( 0, 0 )
+1/modulate = Color( 1, 1, 1, 1 )
+1/region = Rect2( 32, 0, 32, 32 )
+1/tile_mode = 0
+1/occluder_offset = Vector2( 0, 0 )
+1/navigation_offset = Vector2( 0, 0 )
+1/shape_offset = Vector2( 0, 0 )
+1/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+1/shape_one_way = false
+1/shape_one_way_margin = 0.0
+1/shapes = [ ]
+1/z_index = 0
+2/name = "Plants.png 2"
+2/texture = ExtResource( 1 )
+2/tex_offset = Vector2( 0, 0 )
+2/modulate = Color( 1, 1, 1, 1 )
+2/region = Rect2( 64, 0, 32, 32 )
+2/tile_mode = 0
+2/occluder_offset = Vector2( 0, 0 )
+2/navigation_offset = Vector2( 0, 0 )
+2/shape_offset = Vector2( 0, 0 )
+2/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+2/shape_one_way = false
+2/shape_one_way_margin = 0.0
+2/shapes = [ ]
+2/z_index = 0
+3/name = "Plants.png 3"
+3/texture = ExtResource( 1 )
+3/tex_offset = Vector2( 0, 0 )
+3/modulate = Color( 1, 1, 1, 1 )
+3/region = Rect2( 96, 0, 32, 32 )
+3/tile_mode = 0
+3/occluder_offset = Vector2( 0, 0 )
+3/navigation_offset = Vector2( 0, 0 )
+3/shape_offset = Vector2( 0, 0 )
+3/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+3/shape_one_way = false
+3/shape_one_way_margin = 0.0
+3/shapes = [ ]
+3/z_index = 0
+4/name = "Plants.png 4"
+4/texture = ExtResource( 1 )
+4/tex_offset = Vector2( 0, 0 )
+4/modulate = Color( 1, 1, 1, 1 )
+4/region = Rect2( 0, 0, 32, 32 )
+4/tile_mode = 0
+4/occluder_offset = Vector2( 0, 0 )
+4/navigation_offset = Vector2( 0, 0 )
+4/shape_offset = Vector2( 0, 0 )
+4/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+4/shape_one_way = false
+4/shape_one_way_margin = 0.0
+4/shapes = [ ]
+4/z_index = 0
+5/name = "Plants.png 5"
+5/texture = ExtResource( 1 )
+5/tex_offset = Vector2( 0, 0 )
+5/modulate = Color( 1, 1, 1, 1 )
+5/region = Rect2( 128, 0, 32, 32 )
+5/tile_mode = 0
+5/occluder_offset = Vector2( 0, 0 )
+5/navigation_offset = Vector2( 0, 0 )
+5/shape_offset = Vector2( 0, 0 )
+5/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+5/shape_one_way = false
+5/shape_one_way_margin = 0.0
+5/shapes = [ ]
+5/z_index = 0
+6/name = "Plants.png 6"
+6/texture = ExtResource( 1 )
+6/tex_offset = Vector2( 0, 0 )
+6/modulate = Color( 1, 1, 1, 1 )
+6/region = Rect2( 160, 0, 32, 32 )
+6/tile_mode = 0
+6/occluder_offset = Vector2( 0, 0 )
+6/navigation_offset = Vector2( 0, 0 )
+6/shape_offset = Vector2( 0, 0 )
+6/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+6/shape_one_way = false
+6/shape_one_way_margin = 0.0
+6/shapes = [ ]
+6/z_index = 0
+7/name = "Plants.png 7"
+7/texture = ExtResource( 1 )
+7/tex_offset = Vector2( 0, 0 )
+7/modulate = Color( 1, 1, 1, 1 )
+7/region = Rect2( 192, 0, 32, 32 )
+7/tile_mode = 0
+7/occluder_offset = Vector2( 0, 0 )
+7/navigation_offset = Vector2( 0, 0 )
+7/shape_offset = Vector2( 0, 0 )
+7/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+7/shape_one_way = false
+7/shape_one_way_margin = 0.0
+7/shapes = [ ]
+7/z_index = 0
+8/name = "Plants.png 8"
+8/texture = ExtResource( 1 )
+8/tex_offset = Vector2( 0, 0 )
+8/modulate = Color( 1, 1, 1, 1 )
+8/region = Rect2( 224, 0, 32, 32 )
+8/tile_mode = 0
+8/occluder_offset = Vector2( 0, 0 )
+8/navigation_offset = Vector2( 0, 0 )
+8/shape_offset = Vector2( 0, 0 )
+8/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+8/shape_one_way = false
+8/shape_one_way_margin = 0.0
+8/shapes = [ ]
+8/z_index = 0
+9/name = "Plants.png 9"
+9/texture = ExtResource( 1 )
+9/tex_offset = Vector2( 0, 0 )
+9/modulate = Color( 1, 1, 1, 1 )
+9/region = Rect2( 256, 0, 32, 32 )
+9/tile_mode = 0
+9/occluder_offset = Vector2( 0, 0 )
+9/navigation_offset = Vector2( 0, 0 )
+9/shape_offset = Vector2( 0, 0 )
+9/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+9/shape_one_way = false
+9/shape_one_way_margin = 0.0
+9/shapes = [ ]
+9/z_index = 0
+10/name = "Plants.png 10"
+10/texture = ExtResource( 1 )
+10/tex_offset = Vector2( 0, 0 )
+10/modulate = Color( 1, 1, 1, 1 )
+10/region = Rect2( 288, 0, 32, 32 )
+10/tile_mode = 0
+10/occluder_offset = Vector2( 0, 0 )
+10/navigation_offset = Vector2( 0, 0 )
+10/shape_offset = Vector2( 0, 0 )
+10/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+10/shape_one_way = false
+10/shape_one_way_margin = 0.0
+10/shapes = [ ]
+10/z_index = 0
+11/name = "Plants.png 11"
+11/texture = ExtResource( 1 )
+11/tex_offset = Vector2( 0, 0 )
+11/modulate = Color( 1, 1, 1, 1 )
+11/region = Rect2( 320, 0, 32, 32 )
+11/tile_mode = 0
+11/occluder_offset = Vector2( 0, 0 )
+11/navigation_offset = Vector2( 0, 0 )
+11/shape_offset = Vector2( 0, 0 )
+11/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+11/shape_one_way = false
+11/shape_one_way_margin = 0.0
+11/shapes = [ ]
+11/z_index = 0
diff --git a/Other/Vloer.tres b/Other/Vloer.tres
new file mode 100644
index 0000000..9708ad5
--- /dev/null
+++ b/Other/Vloer.tres
@@ -0,0 +1,202 @@
+[gd_resource type="TileSet" load_steps=2 format=2]
+
+[ext_resource path="res://omgeving/vloer32x32/TilesetGodotVloer.png" type="Texture" id=1]
+
+[resource]
+0/name = "Vloer 0"
+0/texture = ExtResource( 1 )
+0/tex_offset = Vector2( 0, 0 )
+0/modulate = Color( 1, 1, 1, 1 )
+0/region = Rect2( 0, 0, -1, -1 )
+0/tile_mode = 2
+0/autotile/icon_coordinate = Vector2( 0, 0 )
+0/autotile/tile_size = Vector2( 32, 32 )
+0/autotile/spacing = 0
+0/autotile/occluder_map = [ ]
+0/autotile/navpoly_map = [ ]
+0/autotile/priority_map = [ ]
+0/autotile/z_index_map = [ ]
+0/occluder_offset = Vector2( 0, 0 )
+0/navigation_offset = Vector2( 0, 0 )
+0/shape_offset = Vector2( 0, 0 )
+0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+0/shape_one_way = false
+0/shape_one_way_margin = 0.0
+0/shapes = [ ]
+0/z_index = 0
+1/name = "TilesetGodotVloer.png 1"
+1/texture = ExtResource( 1 )
+1/tex_offset = Vector2( 0, 0 )
+1/modulate = Color( 1, 1, 1, 1 )
+1/region = Rect2( -32, 0, 32, 32 )
+1/tile_mode = 1
+1/autotile/bitmask_mode = 0
+1/autotile/bitmask_flags = [ ]
+1/autotile/icon_coordinate = Vector2( 0, 0 )
+1/autotile/tile_size = Vector2( 32, 32 )
+1/autotile/spacing = 0
+1/autotile/occluder_map = [ ]
+1/autotile/navpoly_map = [ ]
+1/autotile/priority_map = [ ]
+1/autotile/z_index_map = [ ]
+1/occluder_offset = Vector2( 0, 0 )
+1/navigation_offset = Vector2( 0, 0 )
+1/shape_offset = Vector2( 0, 0 )
+1/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+1/shape_one_way = false
+1/shape_one_way_margin = 0.0
+1/shapes = [ ]
+1/z_index = 0
+2/name = "TilesetGodotVloer.png 2"
+2/texture = ExtResource( 1 )
+2/tex_offset = Vector2( 0, 0 )
+2/modulate = Color( 1, 1, 1, 1 )
+2/region = Rect2( 0, 0, 176, 176 )
+2/tile_mode = 1
+2/autotile/bitmask_mode = 0
+2/autotile/bitmask_flags = [ ]
+2/autotile/icon_coordinate = Vector2( 0, 0 )
+2/autotile/tile_size = Vector2( 32, 32 )
+2/autotile/spacing = 0
+2/autotile/occluder_map = [ ]
+2/autotile/navpoly_map = [ ]
+2/autotile/priority_map = [ ]
+2/autotile/z_index_map = [ ]
+2/occluder_offset = Vector2( 0, 0 )
+2/navigation_offset = Vector2( 0, 0 )
+2/shape_offset = Vector2( 0, 0 )
+2/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+2/shape_one_way = false
+2/shape_one_way_margin = 0.0
+2/shapes = [ ]
+2/z_index = 0
+3/name = "TilesetGodotVloer.png 3"
+3/texture = ExtResource( 1 )
+3/tex_offset = Vector2( 0, 0 )
+3/modulate = Color( 1, 1, 1, 1 )
+3/region = Rect2( 352, 0, 176, 176 )
+3/tile_mode = 1
+3/autotile/bitmask_mode = 0
+3/autotile/bitmask_flags = [ ]
+3/autotile/icon_coordinate = Vector2( 0, 0 )
+3/autotile/tile_size = Vector2( 176, 176 )
+3/autotile/spacing = 0
+3/autotile/occluder_map = [ ]
+3/autotile/navpoly_map = [ ]
+3/autotile/priority_map = [ ]
+3/autotile/z_index_map = [ ]
+3/occluder_offset = Vector2( 0, 0 )
+3/navigation_offset = Vector2( 0, 0 )
+3/shape_offset = Vector2( 0, 0 )
+3/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+3/shape_one_way = false
+3/shape_one_way_margin = 0.0
+3/shapes = [ ]
+3/z_index = 0
+4/name = "TilesetGodotVloer.png 4"
+4/texture = ExtResource( 1 )
+4/tex_offset = Vector2( 0, 0 )
+4/modulate = Color( 1, 1, 1, 1 )
+4/region = Rect2( 0, 0, 176, 176 )
+4/tile_mode = 1
+4/autotile/bitmask_mode = 0
+4/autotile/bitmask_flags = [ ]
+4/autotile/icon_coordinate = Vector2( 0, 0 )
+4/autotile/tile_size = Vector2( 176, 176 )
+4/autotile/spacing = 0
+4/autotile/occluder_map = [ ]
+4/autotile/navpoly_map = [ ]
+4/autotile/priority_map = [ ]
+4/autotile/z_index_map = [ ]
+4/occluder_offset = Vector2( 0, 0 )
+4/navigation_offset = Vector2( 0, 0 )
+4/shape_offset = Vector2( 0, 0 )
+4/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+4/shape_one_way = false
+4/shape_one_way_margin = 0.0
+4/shapes = [ ]
+4/z_index = 0
+5/name = "TilesetGodotVloer.png 5"
+5/texture = ExtResource( 1 )
+5/tex_offset = Vector2( 0, 0 )
+5/modulate = Color( 1, 1, 1, 1 )
+5/region = Rect2( 176, 0, 176, 176 )
+5/tile_mode = 0
+5/occluder_offset = Vector2( 0, 0 )
+5/navigation_offset = Vector2( 0, 0 )
+5/shape_offset = Vector2( 0, 0 )
+5/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+5/shape_one_way = false
+5/shape_one_way_margin = 0.0
+5/shapes = [ ]
+5/z_index = 0
+6/name = "TilesetGodotVloer.png 6"
+6/texture = ExtResource( 1 )
+6/tex_offset = Vector2( 0, 0 )
+6/modulate = Color( 1, 1, 1, 1 )
+6/region = Rect2( 352, 0, 176, 176 )
+6/tile_mode = 0
+6/occluder_offset = Vector2( 0, 0 )
+6/navigation_offset = Vector2( 0, 0 )
+6/shape_offset = Vector2( 0, 0 )
+6/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+6/shape_one_way = false
+6/shape_one_way_margin = 0.0
+6/shapes = [ ]
+6/z_index = 0
+7/name = "TilesetGodotVloer.png 7"
+7/texture = ExtResource( 1 )
+7/tex_offset = Vector2( 0, 0 )
+7/modulate = Color( 1, 1, 1, 1 )
+7/region = Rect2( 528, 0, 176, 176 )
+7/tile_mode = 0
+7/occluder_offset = Vector2( 0, 0 )
+7/navigation_offset = Vector2( 0, 0 )
+7/shape_offset = Vector2( 0, 0 )
+7/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+7/shape_one_way = false
+7/shape_one_way_margin = 0.0
+7/shapes = [ ]
+7/z_index = 0
+8/name = "TilesetGodotVloer.png 8"
+8/texture = ExtResource( 1 )
+8/tex_offset = Vector2( 0, 0 )
+8/modulate = Color( 1, 1, 1, 1 )
+8/region = Rect2( 704, 0, 176, 176 )
+8/tile_mode = 0
+8/occluder_offset = Vector2( 0, 0 )
+8/navigation_offset = Vector2( 0, 0 )
+8/shape_offset = Vector2( 0, 0 )
+8/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+8/shape_one_way = false
+8/shape_one_way_margin = 0.0
+8/shapes = [ ]
+8/z_index = 0
+9/name = "TilesetGodotVloer.png 9"
+9/texture = ExtResource( 1 )
+9/tex_offset = Vector2( 0, 0 )
+9/modulate = Color( 1, 1, 1, 1 )
+9/region = Rect2( 880, 0, 176, 176 )
+9/tile_mode = 0
+9/occluder_offset = Vector2( 0, 0 )
+9/navigation_offset = Vector2( 0, 0 )
+9/shape_offset = Vector2( 0, 0 )
+9/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+9/shape_one_way = false
+9/shape_one_way_margin = 0.0
+9/shapes = [ ]
+9/z_index = 0
+10/name = "TilesetGodotVloer.png 10"
+10/texture = ExtResource( 1 )
+10/tex_offset = Vector2( 0, 0 )
+10/modulate = Color( 1, 1, 1, 1 )
+10/region = Rect2( 1056, 0, 176, 176 )
+10/tile_mode = 0
+10/occluder_offset = Vector2( 0, 0 )
+10/navigation_offset = Vector2( 0, 0 )
+10/shape_offset = Vector2( 0, 0 )
+10/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
+10/shape_one_way = false
+10/shape_one_way_margin = 0.0
+10/shapes = [ ]
+10/z_index = 0
diff --git a/Other/default_env.tres b/Other/default_env.tres
new file mode 100644
index 0000000..3e42e6f
Binary files /dev/null and b/Other/default_env.tres differ
diff --git a/Other/ring_of_races_font.tres b/Other/ring_of_races_font.tres
new file mode 100644
index 0000000..92adcc4
--- /dev/null
+++ b/Other/ring_of_races_font.tres
@@ -0,0 +1,5 @@
+[gd_resource type="DynamicFontData" format=2]
+
+[resource]
+resource_local_to_scene = true
+font_path = "res://ring_of_races_font.ttf"
diff --git a/Ring of Races_v03.apk b/Ring of Races_v03.apk
index 0b6b867..33e326d 100644
Binary files a/Ring of Races_v03.apk and b/Ring of Races_v03.apk differ
diff --git a/Ring of Races_v03_GLE2.apk b/Ring of Races_v03_GLE2.apk
new file mode 100644
index 0000000..af53699
Binary files /dev/null and b/Ring of Races_v03_GLE2.apk differ
diff --git a/Rule1.tscn b/Rule1.tscn
index 66e7a61..5fe390e 100644
--- a/Rule1.tscn
+++ b/Rule1.tscn
@@ -1,7 +1,7 @@
[gd_scene load_steps=3 format=2]
-[ext_resource path="res://Vloer.tres" type="TileSet" id=1]
-[ext_resource path="res://Planten.tres" type="TileSet" id=2]
+[ext_resource path="res://Other/Vloer.tres" type="TileSet" id=1]
+[ext_resource path="res://Other/Planten.tres" type="TileSet" id=2]
[node name="Node2D" type="Node2D"]
position = Vector2( 109, 0 )
diff --git a/Storage/Database.gd b/Storage/Database.gd
index 0eddc5e..35b184e 100644
--- a/Storage/Database.gd
+++ b/Storage/Database.gd
@@ -1,7 +1,7 @@
extends Node
const SQLite = preload("res://addons/godot-sqlite/bin/gdsqlite.gdns")
-var path = "res://Storage/World1.db"
+var path = "user://storage.db"
var db_name = "RingOfRaces"
var db = null
var verbose = true
@@ -37,7 +37,11 @@ func OpenConnection():
self.db.path = path
self.db.verbose_mode = verbose
var create = false
+ print(path)
+
+ # This does not seem to work. The file is in the right place, but being recreated everytime. The file is findable in Res:// and C:/ .. But not after the user folder
if !file.file_exists(path):
+ print("File not existing, so creating new db")
create = true
self.db.open_db()
if create:
@@ -53,8 +57,9 @@ func GetInventoryItems():
ret = db.select_rows("player_inventory", "",["*"])
return ret
-func SaveInventory(inventory):
- if(inventory == null or len(inventory) != 40):
+func SaveInventory(player_inventory_items):
+ print("Now on inventory save file")
+ if(player_inventory_items == null or len(player_inventory_items) != 40):
Global.Log("Bad inventory save!", 3)
return
OpenConnectionIfClosed()
diff --git a/export_presets.cfg b/export_presets.cfg
index 3be27f6..5609a06 100644
--- a/export_presets.cfg
+++ b/export_presets.cfg
@@ -7,7 +7,7 @@ custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
-export_path="./Ring of Races_v03.apk"
+export_path="./Ring of Races_v03_GLE2.apk"
patch_list=PoolStringArray( )
script_export_mode=1
script_encryption_key=""
diff --git a/modules/godot_remote/.gitignore b/modules/godot_remote/.gitignore
new file mode 100644
index 0000000..de85c23
--- /dev/null
+++ b/modules/godot_remote/.gitignore
@@ -0,0 +1,39 @@
+# SConstruct
+*.dblite
+__pycache__
+*.pyc
+
+# Visual Studio Cache
+.vs/
+
+# VSCode Cache
+.vscode/
+
+# Binaries
+*.obj
+*.iobj
+*.so
+*.dll
+*.o
+*.dylib
+*.pdb
+*.ipdb
+*.ilk
+*.exe
+*.exp
+*.lib
+*.vcxproj.filters
+*.vcxproj.user
+*.os
+*.out
+api.json
+.import/
+.mono/
+obj/
+libs/
+bin/
+
+# exist only for testing
+test.gd
+
+godot_remote_client/AssetsInSuperSecureAndUn1queF0lder/Textures/SVG/*.png
diff --git a/modules/godot_remote/.gitmodules b/modules/godot_remote/.gitmodules
new file mode 100644
index 0000000..64fc4d4
--- /dev/null
+++ b/modules/godot_remote/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "godot-cpp"]
+ path = godot-cpp
+ url = https://github.com/GodotNativeTools/godot-cpp
diff --git a/modules/godot_remote/Android.mk b/modules/godot_remote/Android.mk
new file mode 100644
index 0000000..efdf487
--- /dev/null
+++ b/modules/godot_remote/Android.mk
@@ -0,0 +1,47 @@
+# Android.mk
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := godot-cpp
+ifeq ($(TARGET_ARCH_ABI),x86)
+ LOCAL_SRC_FILES := godot-cpp/bin/libgodot-cpp.android.release.x86.a
+endif
+ifeq ($(TARGET_ARCH_ABI),x86_64)
+ LOCAL_SRC_FILES := godot-cpp/bin/libgodot-cpp.android.release.x86_64.a
+endif
+ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
+ LOCAL_SRC_FILES := godot-cpp/bin/libgodot-cpp.android.release.armv7.a
+endif
+ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
+ LOCAL_SRC_FILES := godot-cpp/bin/libgodot-cpp.android.release.arm64v8.a
+endif
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := godot_remote.android.release.$(TARGET_ARCH_ABI)
+LOCAL_CPPFLAGS := -std=c++14
+LOCAL_CPP_FEATURES := rtti exceptions
+LOCAL_LDLIBS := -llog
+
+LOCAL_SRC_FILES := \
+godot_remote/GodotRemote.cpp \
+godot_remote/GRClient.cpp \
+godot_remote/GRDevice.cpp \
+godot_remote/GRInputData.cpp \
+godot_remote/GRNotifications.cpp \
+godot_remote/GRPacket.cpp \
+godot_remote/GRResources.cpp \
+godot_remote/GRServer.cpp \
+godot_remote/GRUtils.cpp \
+godot_remote/jpge.cpp \
+godot_remote/register_types.cpp
+
+LOCAL_C_INCLUDES := \
+godot-cpp/godot-headers \
+godot-cpp/include/ \
+godot-cpp/include/core \
+godot-cpp/include/gen \
+
+LOCAL_STATIC_LIBRARIES := godot-cpp
+
+include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
diff --git a/modules/godot_remote/GodotRemoteGDNative_only_for_developing.sln b/modules/godot_remote/GodotRemoteGDNative_only_for_developing.sln
new file mode 100644
index 0000000..3c7c323
--- /dev/null
+++ b/modules/godot_remote/GodotRemoteGDNative_only_for_developing.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30611.23
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GodotRemoteGDNative", "GodotRemoteGDNative_only_for_developing.vcxproj", "{4D452F2C-8FBB-476B-A990-1E9E5DB93D32}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ debug|x64 = debug|x64
+ debug|x86 = debug|x86
+ release|x64 = release|x64
+ release|x86 = release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4D452F2C-8FBB-476B-A990-1E9E5DB93D32}.debug|x64.ActiveCfg = debug|x64
+ {4D452F2C-8FBB-476B-A990-1E9E5DB93D32}.debug|x64.Build.0 = debug|x64
+ {4D452F2C-8FBB-476B-A990-1E9E5DB93D32}.debug|x86.ActiveCfg = debug|Win32
+ {4D452F2C-8FBB-476B-A990-1E9E5DB93D32}.debug|x86.Build.0 = debug|Win32
+ {4D452F2C-8FBB-476B-A990-1E9E5DB93D32}.release|x64.ActiveCfg = release|x64
+ {4D452F2C-8FBB-476B-A990-1E9E5DB93D32}.release|x64.Build.0 = release|x64
+ {4D452F2C-8FBB-476B-A990-1E9E5DB93D32}.release|x86.ActiveCfg = release|Win32
+ {4D452F2C-8FBB-476B-A990-1E9E5DB93D32}.release|x86.Build.0 = release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {2D6E9625-5E11-4AF3-A331-3B18E40A974D}
+ EndGlobalSection
+EndGlobal
diff --git a/modules/godot_remote/GodotRemoteGDNative_only_for_developing.vcxproj b/modules/godot_remote/GodotRemoteGDNative_only_for_developing.vcxproj
new file mode 100644
index 0000000..4e61081
--- /dev/null
+++ b/modules/godot_remote/GodotRemoteGDNative_only_for_developing.vcxproj
@@ -0,0 +1,215 @@
+
+
+
+
+ debug
+ Win32
+
+
+ release
+ Win32
+
+
+ debug
+ x64
+
+
+ release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {4d452f2c-8fbb-476b-a990-1e9e5db93d32}
+ GodotRemoteGDNative
+ 10.0
+ GodotRemoteGDNative
+
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ godot_remote.windows.$(Configuration).$(PlatformArchitecture)
+ $(SolutionDir)\bin\
+ $(SolutionDir)obj\$(PlatformTarget)\
+ $(ProjectDir)godot-cpp\bin;$(LibraryPath)
+ $(ProjectDir)godot-cpp\include;$(ProjectDir)godot-cpp\godot_headers;$(ProjectDir)godot-cpp\include\gen;$(ProjectDir)godot-cpp\include\core;$(IncludePath)
+
+
+ false
+ godot_remote.windows.$(Configuration).$(PlatformArchitecture)
+ $(SolutionDir)\bin\
+ $(SolutionDir)obj\$(PlatformTarget)\
+ $(ProjectDir)godot-cpp\bin;$(LibraryPath)
+ $(ProjectDir)godot-cpp\include;$(ProjectDir)godot-cpp\godot_headers;$(ProjectDir)godot-cpp\include\gen;$(ProjectDir)godot-cpp\include\core;$(IncludePath)
+
+
+ $(ProjectDir)godot-cpp\include;$(ProjectDir)godot-cpp\godot-headers;$(ProjectDir)godot-cpp\include\gen;$(ProjectDir)godot-cpp\include\core;$(IncludePath)
+ $(ProjectDir)godot-cpp\bin;$(LibraryPath)
+ $(SolutionDir)\bin\
+ godot_remote.windows.$(Configuration).$(PlatformArchitecture)
+ $(SolutionDir)obj\$(PlatformTarget)\
+ false
+
+
+ false
+ $(ProjectDir)godot-cpp\include;$(ProjectDir)godot-cpp\godot-headers;$(ProjectDir)godot-cpp\include\gen;$(ProjectDir)godot-cpp\include\core;$(IncludePath)
+ $(ProjectDir)godot-cpp\bin;$(LibraryPath)
+ $(SolutionDir)\bin\
+ godot_remote.windows.$(Configuration).$(PlatformArchitecture)
+ $(SolutionDir)obj\$(PlatformTarget)\
+
+
+
+ Level3
+ false
+ WIN32;_DEBUG;GDNATIVE_LIBRARY;TOOLS_ENABLED;DEBUG_ENABLED;%(PreprocessorDefinitions)
+ true
+ true
+ ProgramDatabase
+ true
+
+
+ NotSet
+ true
+ libgodot-cpp.windows.$(Configuration).$(PlatformArchitecture).lib;
+
+
+
+
+ Level3
+ true
+ true
+ false
+ WIN32;GDNATIVE_LIBRARY;TOOLS_ENABLED;NDEBUG;%(PreprocessorDefinitions)
+ true
+ true
+
+
+ NotSet
+ true
+ true
+ false
+ libgodot-cpp.windows.$(Configuration).$(PlatformArchitecture).lib;
+
+
+
+
+ Level3
+ false
+ true
+ WIN64;_DEBUG;GDNATIVE_LIBRARY;TOOLS_ENABLED;DEBUG_ENABLED;%(PreprocessorDefinitions)
+ true
+ Sync
+ true
+ ProgramDatabase
+ stdcpp14
+
+
+ NotSet
+ true
+ libgodot-cpp.windows.$(Configuration).$(PlatformArchitecture).lib;
+ $(OutDir)$(TargetName)$(TargetExt)
+ UseLinkTimeCodeGeneration
+
+
+
+
+ Level3
+
+
+ false
+ false
+ WIN64;GDNATIVE_LIBRARY;TOOLS_ENABLED;NDEBUG;%(PreprocessorDefinitions)
+ true
+ true
+ Sync
+
+
+ NotSet
+ true
+ true
+ false
+ libgodot-cpp.windows.$(Configuration).$(PlatformArchitecture).lib;
+ $(OutDir)$(TargetName)$(TargetExt)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/godot_remote/Images/Icons/Close_icon.png b/modules/godot_remote/Images/Icons/Close_icon.png
new file mode 100644
index 0000000..75d41c7
Binary files /dev/null and b/modules/godot_remote/Images/Icons/Close_icon.png differ
diff --git a/modules/godot_remote/Images/Icons/Close_icon.png.import b/modules/godot_remote/Images/Icons/Close_icon.png.import
new file mode 100644
index 0000000..947c653
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Close_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Close_icon.png-55bdce32d084d5e5f806a4a5d837ea4b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Close_icon.png"
+dest_files=[ "res://.import/Close_icon.png-55bdce32d084d5e5f806a4a5d837ea4b.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Close_icon.svg b/modules/godot_remote/Images/Icons/Close_icon.svg
new file mode 100644
index 0000000..4147c7b
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Close_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/godot_remote/Images/Icons/Close_icon.svg.import b/modules/godot_remote/Images/Icons/Close_icon.svg.import
new file mode 100644
index 0000000..6f325c5
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Close_icon.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Close_icon.svg-eb6caf74ca6969dc2a8ada8a0b98f1d5.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Close_icon.svg"
+dest_files=[ "res://.import/Close_icon.svg-eb6caf74ca6969dc2a8ada8a0b98f1d5.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Connected_icon.png b/modules/godot_remote/Images/Icons/Connected_icon.png
new file mode 100644
index 0000000..4e66d5b
Binary files /dev/null and b/modules/godot_remote/Images/Icons/Connected_icon.png differ
diff --git a/modules/godot_remote/Images/Icons/Connected_icon.png.import b/modules/godot_remote/Images/Icons/Connected_icon.png.import
new file mode 100644
index 0000000..77c4c97
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Connected_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Connected_icon.png-e646ca1b6ff2d860a81b4e89f1619f50.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Connected_icon.png"
+dest_files=[ "res://.import/Connected_icon.png-e646ca1b6ff2d860a81b4e89f1619f50.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Connected_icon.svg b/modules/godot_remote/Images/Icons/Connected_icon.svg
new file mode 100644
index 0000000..4a22c6f
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Connected_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/godot_remote/Images/Icons/Connected_icon.svg.import b/modules/godot_remote/Images/Icons/Connected_icon.svg.import
new file mode 100644
index 0000000..e76a773
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Connected_icon.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Connected_icon.svg-91c77637b50d393da88a03a3c4a31090.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Connected_icon.svg"
+dest_files=[ "res://.import/Connected_icon.svg-91c77637b50d393da88a03a3c4a31090.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Disconnected_icon.png b/modules/godot_remote/Images/Icons/Disconnected_icon.png
new file mode 100644
index 0000000..6312ee8
Binary files /dev/null and b/modules/godot_remote/Images/Icons/Disconnected_icon.png differ
diff --git a/modules/godot_remote/Images/Icons/Disconnected_icon.png.import b/modules/godot_remote/Images/Icons/Disconnected_icon.png.import
new file mode 100644
index 0000000..064549c
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Disconnected_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Disconnected_icon.png-ec94adf71229facb1d8116765a16fdcd.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Disconnected_icon.png"
+dest_files=[ "res://.import/Disconnected_icon.png-ec94adf71229facb1d8116765a16fdcd.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Disconnected_icon.svg b/modules/godot_remote/Images/Icons/Disconnected_icon.svg
new file mode 100644
index 0000000..ac3060e
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Disconnected_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/godot_remote/Images/Icons/Disconnected_icon.svg.import b/modules/godot_remote/Images/Icons/Disconnected_icon.svg.import
new file mode 100644
index 0000000..00d5f04
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Disconnected_icon.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Disconnected_icon.svg-8ac048c62e1e64861e3552cc93c93d53.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Disconnected_icon.svg"
+dest_files=[ "res://.import/Disconnected_icon.svg-8ac048c62e1e64861e3552cc93c93d53.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Error_icon.png b/modules/godot_remote/Images/Icons/Error_icon.png
new file mode 100644
index 0000000..9aea76c
Binary files /dev/null and b/modules/godot_remote/Images/Icons/Error_icon.png differ
diff --git a/modules/godot_remote/Images/Icons/Error_icon.png.import b/modules/godot_remote/Images/Icons/Error_icon.png.import
new file mode 100644
index 0000000..979ea54
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Error_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Error_icon.png-3f424e587d3cb0431a257db9d2ea385d.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Error_icon.png"
+dest_files=[ "res://.import/Error_icon.png-3f424e587d3cb0431a257db9d2ea385d.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Error_icon.svg b/modules/godot_remote/Images/Icons/Error_icon.svg
new file mode 100644
index 0000000..05e5480
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Error_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/godot_remote/Images/Icons/Error_icon.svg.import b/modules/godot_remote/Images/Icons/Error_icon.svg.import
new file mode 100644
index 0000000..650cb4f
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Error_icon.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Error_icon.svg-b652e469ed1378493bd3d78653637703.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Error_icon.svg"
+dest_files=[ "res://.import/Error_icon.svg-b652e469ed1378493bd3d78653637703.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Warning_icon.png b/modules/godot_remote/Images/Icons/Warning_icon.png
new file mode 100644
index 0000000..c06a1f6
Binary files /dev/null and b/modules/godot_remote/Images/Icons/Warning_icon.png differ
diff --git a/modules/godot_remote/Images/Icons/Warning_icon.png.import b/modules/godot_remote/Images/Icons/Warning_icon.png.import
new file mode 100644
index 0000000..d3cd095
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Warning_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Warning_icon.png-3b98f3fb52d2ad40673fed73dff42ea6.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Warning_icon.png"
+dest_files=[ "res://.import/Warning_icon.png-3b98f3fb52d2ad40673fed73dff42ea6.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Icons/Warning_icon.svg b/modules/godot_remote/Images/Icons/Warning_icon.svg
new file mode 100644
index 0000000..698288d
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Warning_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/godot_remote/Images/Icons/Warning_icon.svg.import b/modules/godot_remote/Images/Icons/Warning_icon.svg.import
new file mode 100644
index 0000000..48de049
--- /dev/null
+++ b/modules/godot_remote/Images/Icons/Warning_icon.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Warning_icon.svg-d7cff469ecb7ebc653295abfc7c04800.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Icons/Warning_icon.svg"
+dest_files=[ "res://.import/Warning_icon.svg-d7cff469ecb7ebc653295abfc7c04800.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/NoSignal/NoSignal.pdn b/modules/godot_remote/Images/NoSignal/NoSignal.pdn
new file mode 100644
index 0000000..6d48bc6
Binary files /dev/null and b/modules/godot_remote/Images/NoSignal/NoSignal.pdn differ
diff --git a/modules/godot_remote/Images/NoSignal/NoSignal.png b/modules/godot_remote/Images/NoSignal/NoSignal.png
new file mode 100644
index 0000000..98509e7
Binary files /dev/null and b/modules/godot_remote/Images/NoSignal/NoSignal.png differ
diff --git a/modules/godot_remote/Images/NoSignal/NoSignal.png.import b/modules/godot_remote/Images/NoSignal/NoSignal.png.import
new file mode 100644
index 0000000..0220536
--- /dev/null
+++ b/modules/godot_remote/Images/NoSignal/NoSignal.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/NoSignal.png-27de0974ec5cc467f5b572d6723aefde.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/NoSignal/NoSignal.png"
+dest_files=[ "res://.import/NoSignal.png-27de0974ec5cc467f5b572d6723aefde.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/NoSignal/NoSignalVertical.pdn b/modules/godot_remote/Images/NoSignal/NoSignalVertical.pdn
new file mode 100644
index 0000000..56ca817
Binary files /dev/null and b/modules/godot_remote/Images/NoSignal/NoSignalVertical.pdn differ
diff --git a/modules/godot_remote/Images/NoSignal/NoSignalVertical.png b/modules/godot_remote/Images/NoSignal/NoSignalVertical.png
new file mode 100644
index 0000000..b999bd7
Binary files /dev/null and b/modules/godot_remote/Images/NoSignal/NoSignalVertical.png differ
diff --git a/modules/godot_remote/Images/NoSignal/NoSignalVertical.png.import b/modules/godot_remote/Images/NoSignal/NoSignalVertical.png.import
new file mode 100644
index 0000000..c0745bf
--- /dev/null
+++ b/modules/godot_remote/Images/NoSignal/NoSignalVertical.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/NoSignalVertical.png-9f616e3a19712373e669287fcd3cf0a9.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/NoSignal/NoSignalVertical.png"
+dest_files=[ "res://.import/NoSignalVertical.png-9f616e3a19712373e669287fcd3cf0a9.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Screenshots/mobile_settings.png b/modules/godot_remote/Images/Screenshots/mobile_settings.png
new file mode 100644
index 0000000..fb7a03d
Binary files /dev/null and b/modules/godot_remote/Images/Screenshots/mobile_settings.png differ
diff --git a/modules/godot_remote/Images/Screenshots/mobile_settings.png.import b/modules/godot_remote/Images/Screenshots/mobile_settings.png.import
new file mode 100644
index 0000000..3d65662
--- /dev/null
+++ b/modules/godot_remote/Images/Screenshots/mobile_settings.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/mobile_settings.png-796e6d8df2995de699cde1a8e2870e85.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Screenshots/mobile_settings.png"
+dest_files=[ "res://.import/mobile_settings.png-796e6d8df2995de699cde1a8e2870e85.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/Images/Screenshots/settings.png b/modules/godot_remote/Images/Screenshots/settings.png
new file mode 100644
index 0000000..c0f9dce
Binary files /dev/null and b/modules/godot_remote/Images/Screenshots/settings.png differ
diff --git a/modules/godot_remote/Images/Screenshots/settings.png.import b/modules/godot_remote/Images/Screenshots/settings.png.import
new file mode 100644
index 0000000..d6c3728
--- /dev/null
+++ b/modules/godot_remote/Images/Screenshots/settings.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/settings.png-f8eef4d7d1f2459d5277f22ea0ef6cb3.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://modules/godot_remote/Images/Screenshots/settings.png"
+dest_files=[ "res://.import/settings.png-f8eef4d7d1f2459d5277f22ea0ef6cb3.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/LICENSE b/modules/godot_remote/LICENSE
new file mode 100644
index 0000000..e38bbb6
--- /dev/null
+++ b/modules/godot_remote/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2021 DmitriySalnikov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the Software), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, andor sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/modules/godot_remote/README.md b/modules/godot_remote/README.md
new file mode 100644
index 0000000..3488aa7
--- /dev/null
+++ b/modules/godot_remote/README.md
@@ -0,0 +1,723 @@
+# Godot Remote
+
+This is cross platform native module for [Godot Engine](https://github.com/godotengine/godot) v3 for control apps and games over WiFi or ADB.
+
+If you are developing on a non-touch device, this module is the best way to quickly test touch input or test mobile sensors data.
+
+[Video Demonstration](https://youtu.be/LbFcQnS3z3E)
+
+[Custom Packets Demo](https://youtu.be/RmhppDWZZk8)
+
+## Support
+
+[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I53VZ2D)
+
+[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/dmitriysalnikov)
+
+## Compiling the Module
+
+### As a module
+
+1. [configure environment](https://docs.godotengine.org/en/3.2/development/compiling/index.html) to build editor for your platform (you need to clone [3.2 branch](https://github.com/godotengine/godot/tree/3.2) not master)
+2. copy ```godot_remote``` folder to the ```modules/``` directory or make [symlink](https://en.wikipedia.org/wiki/Symbolic_link)
+3. compile engine with instructions from documentation above (e.g. ```scons p=windows tools=yes -j[place here count of your CPU threads]```)
+4. run ```bin/godot[based on config]```.
+
+If everything compiles successfully, you'll find the new category in project settings ```Debug/Godot Remote``` where you can configure server.
+
+![Settings](Images/Screenshots/settings.png)
+
+### As a GDNative library
+
+1. [Configure environment](https://docs.godotengine.org/en/3.2/development/compiling/index.html) to build editor for your platform
+2. Generate api.json for GDNative api. ```bin/godot --gdnative-generate-json-api api.json```
+3. Copy api.json to the root directory of this repository
+4. Compile godot-cpp (e.g. in godot-cpp directory run ```scons generate_bindings=true platform=windows target=release bits=64 -j8 ../api.json```)
+5. Compile module for your platform (Available platforms: windows, osx, linux, ios, android. Tested platforms: windows, linux, android)
+ 1. For android: Run in root directory ```[path to your android ndk root dir]/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk APP_PLATFORM=android-21```
+ 2. For all other platforms: ```scons platform=windows target=release -j8```
+6. Use produced library in ```bin/```
+
+GDNative has limitations so here ```GodotRemote``` is not a singleton and you need to create autoload scene with attached NativeScript for ```GodotRemote``` class. Also there is no any settings in ```Debug/Godot Remote```.
+
+Enum constants in this version changed too (see [API Reference] )
+
+**Currently, the GDNative version does not support the assignment of sensor data, so the editor will not support accelerometer, gyroscope, etc.
+Also, this version may crash at a random moment.**
+
+If GDNative becomes more stable, I will add the necessary code to easily integrate this module into any project, but now it just works.. sometimes.
+
+### Additional parameters
+
+Also module has additional compilation parameters for scons script
+
+1. ```godot_remote_no_default_resources``` (yes/no) default no - compile with or without default resources
+2. ```godot_remote_disable_server``` (yes/no) default no - do not include server code
+3. ```godot_remote_disable_client``` (yes/no) default no - do not include client code
+
+## Download
+
+Precompiled binaries can be found on [GitHub Releases](https://github.com/DmitriySalnikov/GodotRemote/releases) page
+
+### Mobile app
+
+On releases page you can found precompiled mobile app but also it can be downloaded from [Google Play](https://play.google.com/store/apps/details?id=com.dmitriysalnikov.godotremote)
+
+## Configure Mobile App
+
+To open settings menu you need to touch the screen with 5 fingers at once.
+
+Then you'll see this settings menu:
+
+![Settings](Images/Screenshots/mobile_settings.png)
+
+**Important:** after entering server address you should apply it by pressing `Set Type and Address` or `Set Type and Port`
+
+## Custom client
+
+If need to support other platforms or you need a specific version of module integrated to the client app, you can build client from source code placed [here](godot_remote_client).
+
+If you don't want to use my client app you can check the [example client project](examples/simple_client) and build your own client.
+
+Or you can donate me some money with request to buy iPhone and adapt a client for it 🙂
+
+## API Reference
+
+Methods will be declared follows this template:
+
+```python
+return_type function_name([arg_name1 : type [= defalut value]][, arg_name2 : type [= defalut value]])
+```
+
+**Important:** All enums in GDNative version is exposed in GodotRemote class because of limitations.
+For example, if you want to use StreamState.STREAM_ACTIVE from GRClient you need to get property GRClient_STREAM_ACTIVE of GodotRemote __object__
+
+```python
+# Godot module:
+GRClient.STREAM_ACTIVE:
+
+# GDNative
+# *GodotRemote is autoload scene with attached NativeScript
+GodotRemote.GRClient_STREAM_ACTIVE
+```
+
+### GodotRemote
+
+Main class of module.
+
+```python
+# --- Properties
+
+# Canvas layer that shows notifications
+# type int, default 128
+notifications_layer
+
+# Notifications position on screen
+# type GRNotifications.NotificationsPosition, default TC
+notifications_position
+
+# Is notifications enabled
+# type bool, default true
+notifications_enabled
+
+# Base duration for showing notifications
+# type float, default 3.0
+notifications_duration
+
+# Notifcations style
+# type GRNotificationStyle
+notifications_style
+
+# --- Methods
+
+# Notifications
+
+# Adds or fully update existing notification
+# @title: Notification title
+# @text: Text of notification
+# @notification_icon: Notification icon from enum NotificationIcon
+# @update_existing: Updates existing notification
+# @duration_multiplier: Multiply base notifications duration
+void add_notification(title: String, text: String, notification_icon: GRNotifications.NotificationIcon = 0, update_existing: bool = true, duration_multiplier: float = 1.0)
+
+# Adds new notification or append text to existing notification
+# @title: Notification title
+# @text: Text of notification
+# @icon: Notification icon from enum NotificationIcon
+# @add_to_new_line: Adds text to new line or adds to current line
+void add_notification_or_append_string(title: String, text: String, icon: GRNotifications.NotificationIcon, add_to_new_line: bool = true, duration_multiplier: float = 1.0)
+
+# Adds notification or update one line of notification text
+# @title: Notification title
+# @id: Line ID
+# @text: Text of notification
+# @icon: Notification icon from enum NotificationIcon
+# @duration_multiplier: Multiply base notifications duration
+void add_notification_or_update_line(title: String, id: String, text: String, icon: GRNotifications.NotificationIcon, duration_multiplier: float = 1.0)
+
+# Clear all notifications
+void clear_notifications()
+
+# Get notifications list
+# @return list of all visible notifications
+Array get_all_notifications()
+
+# Get notification with specified title or null
+# @title: Notification title
+# @return matched notification
+GRNotificationPanel get_notification(title: String)
+
+# Get all notifications with specified title
+# @title: Notification title
+# @return list of visible notifications
+Array get_notifications_with_title(title: String)
+
+# Remove notifications with specified title
+# @title: Notifications title
+# @is_all_entries: Delete all notifications with @title if true
+void remove_notification(title: String, is_all_entries: bool = true)
+
+# Remove exact notification by reference
+# @notification: Notification reference
+void remove_notification_exact(notification: Node)
+
+# Client/Server
+
+# Create device: client or server
+# @device_type: Type of device
+# @return true if device created successful
+bool create_remote_device(device_type: GodotRemote.DeviceType = 0)
+
+# Start device
+# @return true if device valid
+bool start_remote_device()
+
+# Create and start device
+# @device_type: Type of device
+void create_and_start_device(device_type: GodotRemote.DeviceType = 0)
+
+# Remove and delete currently working device
+# @return true if succeed
+bool remove_remote_device()
+
+# Get device
+# @return client, server or null
+GRDevice get_device()
+
+# Utility functions
+
+# Not exposed to GDScript fuctions from Input class
+# And currently not available in GDNative
+void set_accelerometer(value: Vector3)
+void set_gravity(value: Vector3)
+void set_gyroscope(value: Vector3)
+void set_magnetometer(value: Vector3)
+
+# Set GodotRemote log level
+# @level: Level of logging
+void set_log_level(level: LogLevel)
+
+# Get GodotRemote module version
+# @return module version in format "MAJOR.MINOR.BUILD"
+String get_version()
+
+# --- Signals
+
+# Device added
+device_added()
+
+# Device removed
+device_removed()
+
+# --- Enumerations
+
+DeviceType:
+ DEVICE_AUTO = 0
+ DEVICE_SERVER = 1
+ DEVICE_CLIENT = 2
+
+LogLevel:
+ LL_NONE = 4
+ LL_DEBUG = 0
+ LL_NORMAL = 1
+ LL_WARNING = 2
+ LL_ERROR = 3
+```
+
+### GRNotifications
+
+Container for all notifications
+
+```python
+
+# --- Signals
+
+# Called when a single notification is added
+notification_added(title: String, text: String)
+
+# Called when a single notification is removed
+notification_removed(title: String, is_cleared: bool)
+
+# Called when all notifications are cleared
+notifications_cleared()
+
+# Called when notifications are enabled or disabled
+notifications_toggled(is_enabled: bool)
+
+# --- Enumerations
+
+NotificationIcon:
+ ICON_NONE = 0
+ ICON_ERROR = 1
+ ICON_WARNING = 2
+ ICON_SUCCESS = 3
+ ICON_FAIL = 4
+
+NotificationsPosition:
+ TOP_LEFT = 0
+ TOP_CENTER = 1
+ TOP_RIGHT = 2
+ BOTTOM_LEFT = 3
+ BOTTOM_CENTER = 4
+ BOTTOM_RIGHT = 5
+```
+
+### GRNotificationStyle
+
+Helper class to store parameters of notifications style
+
+```python
+# --- Properties
+
+# Style of background notifications panel
+# type StyleBox
+panel_style
+
+# Theme for notification close button
+# type Theme
+close_button_theme
+
+# Close button icon texture
+# type Texture
+close_button_icon
+
+# Notification title font
+# type Font
+title_font
+
+# Notification text font
+# type Font
+text_font
+
+# --- Methods
+
+# Get notification icon from this style
+# @notification_icon: Notfication icon id
+# @return icon texture of null
+Texture get_notification_icon(notification_icon: GRNotifications.NotificationIcon)
+
+# Set notification icon in this style
+# @notification_icon: Notfication icon id
+# @icon_texture: Icon texture
+void set_notification_icon(notification_icon: GRNotifications.NotificationIcon, icon_texture: Texture)
+
+```
+
+### GRInputData
+
+Container for all InputEvents
+
+```python
+# --- Enumerations
+
+InputType:
+ _NoneIT = 0
+ _InputDeviceSensors = 1
+ _InputEvent = 64
+ _InputEventAction = 65
+ _InputEventGesture = 66
+ _InputEventJoypadButton = 67
+ _InputEventJoypadMotion = 68
+ _InputEventKey = 69
+ _InputEventMagnifyGesture = 70
+ _InputEventMIDI = 71
+ _InputEventMouse = 72
+ _InputEventMouseButton = 73
+ _InputEventMouseMotion = 74
+ _InputEventPanGesture = 75
+ _InputEventScreenDrag = 76
+ _InputEventScreenTouch = 77
+ _InputEventWithModifiers = 78
+ _InputEventMAX = 79
+```
+
+### GRPacket
+
+The basic data type used to exchange information between the client and the server
+
+```python
+# --- Enumerations
+
+PacketType:
+ NonePacket = 0
+ SyncTime = 1
+ ImageData = 2
+ InputData = 3
+ ServerSettings = 4
+ MouseModeSync = 5
+ CustomInputScene = 6
+ ClientStreamOrientation = 7
+ ClientStreamAspect = 8
+ CustomUserData = 9
+ Ping = 128
+ Pong = 192
+```
+
+### GRDevice
+
+Base class for client and server
+
+```python
+# --- Properties
+
+# Connection port
+# type int, default 52341
+port
+
+# --- Methods
+
+# Send user data to remote device
+# @packet_id: any data to identify your packet
+# @user_data: any data to send to remote device
+# @full_objects: flag for full serialization of objects, possibly with their executable code. For more info check Godot's PacketPeer.put_var() and PacketPeer.get_var()
+void send_user_data(packet_id: Variant, user_data: Variant, full_objects: bool = false)
+
+# Get average FPS
+# @return average FPS
+float get_avg_fps()
+
+# Get minimum FPS
+# @return minimum FPS
+float get_min_fps()
+
+# Get maximum FPS
+# @return maximum FPS
+float get_max_fps()
+
+# Get average ping
+# @return average ping
+float get_avg_ping()
+
+# Get minimum ping
+# @return minimum ping
+float get_min_ping()
+
+# Get maximum ping
+# @return maximum ping
+float get_max_ping()
+
+# Get device status
+WorkingStatus get_status()
+
+# Start device
+void start()
+
+# Stop device
+void stop()
+
+# --- Signals
+
+# Device status changed
+status_changed(status: GRDevice.WorkingStatus)
+
+# User data received from a remote device
+user_data_received(packet_id: Variant, user_data: Variant)
+
+# --- Enumerations
+
+ImageCompressionType:
+ COMPRESSION_UNCOMPRESSED = 0
+ COMPRESSION_JPG = 1
+ COMPRESSION_PNG = 2
+
+Subsampling:
+ SUBSAMPLING_Y_ONLY = 0
+ SUBSAMPLING_H1V1 = 1
+ SUBSAMPLING_H2V1 = 2
+ SUBSAMPLING_H2V2 = 3
+
+TypesOfServerSettings:
+ SERVER_SETTINGS_USE_INTERNAL = 0
+ SERVER_SETTINGS_VIDEO_STREAM_ENABLED = 1
+ SERVER_SETTINGS_COMPRESSION_TYPE = 2
+ SERVER_SETTINGS_JPG_QUALITY = 3
+ SERVER_SETTINGS_SKIP_FRAMES = 4
+ SERVER_SETTINGS_RENDER_SCALE = 5
+
+WorkingStatus:
+ STATUS_STARTING = 3
+ STATUS_STOPPING = 2
+ STATUS_WORKING = 1
+ STATUS_STOPPED = 0
+```
+
+### GRServer
+
+```python
+# --- Properties
+
+# Server password
+# type String, default ""
+password
+
+# Path to the custom input scene.
+# type String, default ""
+custom_input_scene
+
+# Is custom input scene compressed
+## Doesn't work in GDNative
+# type bool, default true
+custom_input_scene_compressed
+
+# Compression type of custom input scene
+## Doesn't work in GDNative
+# type File.CompressionMode, default FastLZ
+custom_input_scene_compression_type
+
+# --- Methods
+
+# Set whether the stream is enabled
+bool set_video_stream_enabled(value : bool)
+
+# Get whether the stream is enabled
+bool is_video_stream_enabled()
+
+# Set how many frames to skip
+bool set_skip_frames(frames : int)
+
+# Get the number of skipping frames
+int get_skip_frames()
+
+# Set JPG quality
+bool set_jpg_quality(quality : int)
+
+# Get JPG quality
+int get_jpg_quality()
+
+# Set the scale of the stream
+bool set_render_scale(scale : float)
+
+# Get stream scale
+float get_render_scale()
+
+# Force update custom input scene on client
+void force_update_custom_input_scene()
+
+# Get resize viewport node
+# @return resize viewport or null
+GRSViewport get_gr_viewport()
+
+
+# --- Signals
+
+# On client connected
+client_connected(device_id: String)
+
+# On client disconnected
+client_disconnected(device_id: String)
+
+# On orientation of client's screen or viewport changed
+client_viewport_orientation_changed(is_vertical: bool)
+
+# On client's screen or viewport aspect ratio changed
+client_viewport_aspect_ratio_changed(stream_aspect: float)
+
+```
+
+### GRClient
+
+```python
+# --- Properties
+
+# Capture input only when containing control has focus
+# type bool, default false
+capture_on_focus
+
+# Capture input only when stream image hovered
+# type bool, default true
+capture_when_hover
+
+# Capture mouse pointer and touch events
+# type bool, default true
+capture_pointer
+
+# Capture input
+# type bool, default true
+capture_input
+
+# Type of connection
+# type GRClient.ConnectionType, default CONNECTION_WiFi
+connection_type
+
+# Frequency of sending data to the server
+# type int, default 60
+target_send_fps
+
+# Stretch mode of stream image
+# type GRClient.StretchMode, default STRETCH_KEEP_ASPECT
+stretch_mode
+
+# Use texture filtering of stream image
+# type bool, default true
+texture_filtering
+
+# Password
+# type String, default ""
+password
+
+# ID of device
+# type String, default 6 random digits and characters
+device_id
+
+# Sync viewport orientation with server
+# type bool, default true
+viewport_orientation_syncing
+
+# Sync viewport aspect ratio with server
+# type bool, default true
+viewport_aspect_ratio_syncing
+
+# Receive updated server settings
+# type bool, default false
+server_settings_syncing
+
+# --- Methods
+
+# Restore settings on server
+void disable_overriding_server_settings()
+
+# Get the current visible custom input scene
+# @return: Custom input scene
+Node get_custom_input_scene()
+
+# Get server address
+# @return server address
+String get_address()
+
+# Is connected to server
+# @return true if connected to server
+bool is_connected_to_host()
+
+# Is stream active
+# @return true if stream active
+bool is_stream_active()
+
+# Set server address to connect
+# @ip: IP of server
+# @return true if address is valid
+bool set_address(ip: String)
+
+# Set both server address and port
+# @ip: IP of server
+# @port: Port of server
+# @return true if address is valid
+bool set_address_port(ip: String, port: int)
+
+# Set the control to show stream in
+# @control_node: Control where stream will be shown
+# @position_in_node: Position of stream in parent
+void set_control_to_show_in(control_node: Control, position_in_node: int = 0)
+
+# Set custom material for no signal screen
+# @material: Custom material
+void set_custom_no_signal_material(material: Material)
+
+# Set custom horizontal texture for no signal screen
+# @texture: Custom texture
+void set_custom_no_signal_texture(texture: Texture)
+
+# Set custom vertical texture for no signal screen
+# @texture: Custom texture
+void set_custom_no_signal_vertical_texture(texture: Texture)
+
+# Override setting on server
+# @setting: Which setting need to change
+# @value: Value of setting
+void set_server_setting(setting: GRdevice.TypesOfServerSettings, value: Variant)
+
+# --- Signals
+
+# On custom input scene added and becomes visible
+custom_input_scene_added()
+
+# On custom input scene removed
+custom_input_scene_removed()
+
+# On connection state changed
+connection_state_changed(is_connected: bool)
+
+# On stream state changed
+stream_state_changed(state: GRClient.StreamState)
+
+# On mouse mode changed on server
+mouse_mode_changed(mouse_mode: Input.MouseMode)
+
+# On received server settings from server
+server_settings_received(settings: Dictionary)
+
+# --- Enumerations
+
+ConnectionType:
+ CONNECTION_ADB = 1
+ CONNECTION_WiFi = 0
+
+StreamState:
+ STREAM_NO_SIGNAL = 0
+ STREAM_ACTIVE = 1
+ STREAM_NO_IMAGE = 2
+
+StretchMode:
+ STRETCH_KEEP_ASPECT = 0
+ STRETCH_FILL = 1
+```
+
+There is no need to describe other classes here
+
+## Custom Input Scenes
+
+In custom input scenes you can use everything you want but to send InputEvent's from client to server you must emulate input. Or use the send_user_data() method and user_data_received signal for send and receive custom packets.
+Example:
+
+```python
+# -- With InputEvent's
+
+func _on_pressed():
+ # Create event for pressed state
+ var iea_p = InputEventAction.new()
+ iea_p.pressed = true
+ iea_p.action = "jump"
+ # Create event for released state
+ var iea_r = InputEventAction.new()
+ iea_r.pressed = false
+ iea_p.action = "jump"
+ # Parse event to send it to the server
+ Input.parse_input_event(iea_p)
+ Input.parse_input_event(iea_r)
+
+# -- With custom packets
+
+# on first device
+func _ready():
+ GodotRemote.get_device().connect("user_data_received", self, "_on_user_data_received")
+
+func _on_user_data_received(id, data):
+ print("Received packet: %s, data: %s" % [id, data])
+
+# on second device
+func _on_button_pressed():
+ GodotRemote.get_device().send_user_data("bg_color", color, false)
+```
+
+## License
+
+MIT license
diff --git a/modules/godot_remote/SConstruct b/modules/godot_remote/SConstruct
new file mode 100644
index 0000000..d5bb421
--- /dev/null
+++ b/modules/godot_remote/SConstruct
@@ -0,0 +1,470 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import subprocess
+
+if sys.version_info < (3,):
+ def decode_utf8(x):
+ return x
+else:
+ import codecs
+ def decode_utf8(x):
+ return codecs.utf_8_decode(x)[0]
+
+# Workaround for MinGW. See:
+# http://www.scons.org/wiki/LongCmdLinesOnWin32
+if (os.name=="nt"):
+ import subprocess
+
+ def mySubProcess(cmdline,env):
+ #print "SPAWNED : " + cmdline
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, startupinfo=startupinfo, shell = False, env = env)
+ data, err = proc.communicate()
+ rv = proc.wait()
+ if rv:
+ print("=====")
+ print(err.decode("utf-8"))
+ print("=====")
+ return rv
+
+ def mySpawn(sh, escape, cmd, args, env):
+
+ newargs = ' '.join(args[1:])
+ cmdline = cmd + " " + newargs
+
+ rv=0
+ if len(cmdline) > 32000 and cmd.endswith("ar") :
+ cmdline = cmd + " " + args[1] + " " + args[2] + " "
+ for i in range(3,len(args)) :
+ rv = mySubProcess( cmdline + args[i], env )
+ if rv :
+ break
+ else:
+ rv = mySubProcess( cmdline, env )
+
+ return rv
+
+def add_sources(sources, dir, extension):
+ for f in os.listdir(dir):
+ if f.endswith('.' + extension):
+ sources.append(dir + '/' + f)
+
+
+# Try to detect the host platform automatically.
+# This is used if no `platform` argument is passed
+if sys.platform.startswith('linux'):
+ host_platform = 'linux'
+elif sys.platform.startswith('freebsd'):
+ host_platform = 'freebsd'
+elif sys.platform == 'darwin':
+ host_platform = 'osx'
+elif sys.platform == 'win32' or sys.platform == 'msys':
+ host_platform = 'windows'
+else:
+ raise ValueError(
+ 'Could not detect platform automatically, please specify with '
+ 'platform='
+ )
+
+env = Environment(ENV = os.environ)
+
+is64 = sys.maxsize > 2**32
+if (
+ env['TARGET_ARCH'] == 'amd64' or
+ env['TARGET_ARCH'] == 'emt64' or
+ env['TARGET_ARCH'] == 'x86_64' or
+ env['TARGET_ARCH'] == 'arm64-v8a'
+):
+ is64 = True
+
+opts = Variables([], ARGUMENTS)
+opts.Add(EnumVariable(
+ 'platform',
+ 'Target platform',
+ host_platform,
+ allowed_values=('linux', 'freebsd', 'osx', 'windows', 'android', 'ios'),
+ ignorecase=2
+))
+opts.Add(EnumVariable(
+ 'bits',
+ 'Target platform bits',
+ '64' if is64 else '32',
+ ('32', '64')
+))
+opts.Add(BoolVariable(
+ 'use_llvm',
+ 'Use the LLVM compiler - only effective when targeting Linux or FreeBSD',
+ False
+))
+opts.Add(BoolVariable(
+ 'use_mingw',
+ 'Use the MinGW compiler instead of MSVC - only effective on Windows',
+ False
+))
+# Must be the same setting as used for cpp_bindings
+opts.Add(EnumVariable(
+ 'target',
+ 'Compilation target',
+ 'debug',
+ allowed_values=('debug', 'release'),
+ ignorecase=2
+))
+opts.Add(PathVariable(
+ 'headers_dir',
+ 'Path to the directory containing Godot headers',
+ 'godot-cpp/godot-headers',
+ PathVariable.PathIsDir
+))
+opts.Add(PathVariable(
+ 'custom_api_file',
+ 'Path to a custom JSON API file',
+ None,
+ PathVariable.PathIsFile
+))
+opts.Add(EnumVariable(
+ 'generate_bindings',
+ 'Generate GDNative API bindings',
+ 'auto',
+ allowed_values = ['yes', 'no', 'auto', 'true'],
+ ignorecase = 2
+))
+opts.Add(EnumVariable(
+ 'android_arch',
+ 'Target Android architecture',
+ 'armv7',
+ ['armv7','arm64v8','x86','x86_64']
+))
+opts.Add(
+ 'macos_deployment_target',
+ 'macOS deployment target',
+ 'default'
+)
+opts.Add(EnumVariable(
+ 'ios_arch',
+ 'Target iOS architecture',
+ 'arm64',
+ ['armv7', 'arm64', 'x86_64']
+))
+opts.Add(BoolVariable(
+ 'ios_simulator',
+ 'Target iOS Simulator',
+ False
+))
+opts.Add(
+ 'IPHONEPATH',
+ 'Path to iPhone toolchain',
+ '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain',
+)
+opts.Add(
+ 'android_api_level',
+ 'Target Android API level',
+ '18' if ARGUMENTS.get("android_arch", 'armv7') in ['armv7', 'x86'] else '21'
+)
+opts.Add(
+ 'ANDROID_NDK_ROOT',
+ 'Path to your Android NDK installation. By default, uses ANDROID_NDK_ROOT from your defined environment variables.',
+ os.environ.get("ANDROID_NDK_ROOT", None)
+)
+opts.Add(BoolVariable(
+ 'generate_template_get_node',
+ "Generate a template version of the Node class's get_node.",
+ True
+))
+
+# GODOT REMOTE CUSTOM OPTIONS
+
+opts.Add(BoolVariable(
+ 'godot_remote_no_default_resources',
+ 'Compile without embeded resources.',
+ False
+))
+opts.Add(BoolVariable(
+ 'godot_remote_disable_server',
+ 'Compile without server side.',
+ False
+))
+opts.Add(BoolVariable(
+ 'godot_remote_disable_client',
+ 'Compile without client side.',
+ False
+))
+
+# END
+
+opts.Update(env)
+Help(opts.GenerateHelpText(env))
+
+# This makes sure to keep the session environment variables on Windows.
+# This way, you can run SCons in a Visual Studio 2017 prompt and it will find
+# all the required tools
+if host_platform == 'windows' and env['platform'] != 'android':
+ if env['bits'] == '64':
+ env = Environment(TARGET_ARCH='amd64')
+ elif env['bits'] == '32':
+ env = Environment(TARGET_ARCH='x86')
+
+ opts.Update(env)
+
+if env['platform'] == 'linux' or env['platform'] == 'freebsd':
+ env['SHLIBSUFFIX'] = '.so'
+ if env['use_llvm']:
+ env['CXX'] = 'clang++'
+
+ env.Append(CCFLAGS=['-fPIC', '-std=c++14', '-Wwrite-strings'])
+ env.Append(LINKFLAGS=["-Wl,-R,'$$ORIGIN'"])
+
+ if env['target'] == 'debug':
+ env.Append(CCFLAGS=['-Og', '-g'])
+ elif env['target'] == 'release':
+ env.Append(CCFLAGS=['-O3'])
+
+ if env['bits'] == '64':
+ env.Append(CCFLAGS=['-m64'])
+ env.Append(LINKFLAGS=['-m64'])
+ elif env['bits'] == '32':
+ env.Append(CCFLAGS=['-m32'])
+ env.Append(LINKFLAGS=['-m32'])
+
+elif env['platform'] == 'osx':
+ # Use Clang on macOS by default
+ env['CXX'] = 'clang++'
+ env['SHLIBSUFFIX'] = '.dylib'
+ if env['bits'] == '32':
+ raise ValueError(
+ 'Only 64-bit builds are supported for the macOS target.'
+ )
+
+ env.Append(CCFLAGS=['-std=c++14', '-arch', 'x86_64'])
+
+ if env['macos_deployment_target'] != 'default':
+ env.Append(CCFLAGS=['-mmacosx-version-min=' + env['macos_deployment_target']])
+
+ env.Append(LINKFLAGS=[
+ '-arch',
+ 'x86_64',
+ '-framework',
+ 'Cocoa',
+ '-Wl,-undefined,dynamic_lookup',
+ ])
+
+ if env['target'] == 'debug':
+ env.Append(CCFLAGS=['-Og', '-g'])
+ elif env['target'] == 'release':
+ env.Append(CCFLAGS=['-O3'])
+
+elif env['platform'] == 'ios':
+ env['SHLIBSUFFIX'] = '.dylib'
+ if env['ios_simulator']:
+ sdk_name = 'iphonesimulator'
+ env.Append(CCFLAGS=['-mios-simulator-version-min=10.0'])
+ env['LIBSUFFIX'] = ".simulator" + env['LIBSUFFIX']
+ else:
+ sdk_name = 'iphoneos'
+ env.Append(CCFLAGS=['-miphoneos-version-min=10.0'])
+
+ try:
+ sdk_path = decode_utf8(subprocess.check_output(['xcrun', '--sdk', sdk_name, '--show-sdk-path']).strip())
+ except (subprocess.CalledProcessError, OSError):
+ raise ValueError("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))
+
+ compiler_path = env['IPHONEPATH'] + '/usr/bin/'
+ env['ENV']['PATH'] = env['IPHONEPATH'] + "/Developer/usr/bin/:" + env['ENV']['PATH']
+
+ env['CC'] = compiler_path + 'clang'
+ env['CXX'] = compiler_path + 'clang++'
+ env['AR'] = compiler_path + 'ar'
+ env['RANLIB'] = compiler_path + 'ranlib'
+
+ env.Append(CCFLAGS=['-std=c++14', '-arch', env['ios_arch'], '-isysroot', sdk_path])
+ env.Append(LINKFLAGS=[
+ '-arch',
+ env['ios_arch'],
+ '-framework',
+ 'Cocoa',
+ '-Wl,-undefined,dynamic_lookup',
+ '-isysroot', sdk_path,
+ '-F' + sdk_path
+ ])
+
+ if env['target'] == 'debug':
+ env.Append(CCFLAGS=['-Og', '-g'])
+ elif env['target'] == 'release':
+ env.Append(CCFLAGS=['-O3'])
+
+elif env['platform'] == 'windows':
+ env['SHLIBSUFFIX'] = '.dll'
+ if host_platform == 'windows' and not env['use_mingw']:
+ # MSVC
+ env.Append(LINKFLAGS=['/WX'])
+ if env['target'] == 'debug':
+ env.Append(CCFLAGS=['/Z7', '/Od', '/EHsc', '/D_DEBUG', '/MDd'])
+ elif env['target'] == 'release':
+ env.Append(CCFLAGS=['/O2', '/EHsc', '/DNDEBUG', '/MD'])
+
+ elif host_platform == 'linux' or host_platform == 'freebsd' or host_platform == 'osx':
+ # Cross-compilation using MinGW
+ if env['bits'] == '64':
+ env['CXX'] = 'x86_64-w64-mingw32-g++'
+ env['AR'] = "x86_64-w64-mingw32-ar"
+ env['RANLIB'] = "x86_64-w64-mingw32-ranlib"
+ env['LINK'] = "x86_64-w64-mingw32-g++"
+ elif env['bits'] == '32':
+ env['CXX'] = 'i686-w64-mingw32-g++'
+ env['AR'] = "i686-w64-mingw32-ar"
+ env['RANLIB'] = "i686-w64-mingw32-ranlib"
+ env['LINK'] = "i686-w64-mingw32-g++"
+
+ elif host_platform == 'windows' and env['use_mingw']:
+ # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
+ env = Environment(ENV = os.environ, tools=["mingw"])
+ opts.Update(env)
+ #env = env.Clone(tools=['mingw'])
+
+ env["SPAWN"] = mySpawn
+
+ # Native or cross-compilation using MinGW
+ if host_platform == 'linux' or host_platform == 'freebsd' or host_platform == 'osx' or env['use_mingw']:
+ # These options are for a release build even using target=debug
+ env.Append(CCFLAGS=['-O3', '-std=c++14', '-Wwrite-strings'])
+ env.Append(LINKFLAGS=[
+ '--static',
+ '-Wl,--no-undefined',
+ '-static-libgcc',
+ '-static-libstdc++',
+ ])
+
+elif env['platform'] == 'android':
+ env['SHLIBSUFFIX'] = '.so'
+
+ if env['target'] == 'debug':
+ env.Append(CCFLAGS=['-Og'])
+ elif env['target'] == 'release':
+ env.Append(CCFLAGS=['-O3'])
+
+ if host_platform == 'windows':
+ # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
+ env = Environment(ENV = os.environ, tools=["mingw"])
+ opts.Update(env)
+ #env = env.Clone(tools=['mingw'])
+
+ env["SPAWN"] = mySpawn
+
+ # Verify NDK root
+ if not 'ANDROID_NDK_ROOT' in env:
+ raise ValueError("To build for Android, ANDROID_NDK_ROOT must be defined. Please set ANDROID_NDK_ROOT to the root folder of your Android NDK installation.")
+
+ # Validate API level
+ api_level = int(env['android_api_level'])
+ if env['android_arch'] in ['x86_64', 'arm64v8'] and api_level < 21:
+ print("WARN: 64-bit Android architectures require an API level of at least 21; setting android_api_level=21")
+ env['android_api_level'] = '21'
+ api_level = 21
+
+ # Setup toolchain
+ toolchain = env['ANDROID_NDK_ROOT'] + "/toolchains/llvm/prebuilt/"
+ if host_platform == "windows":
+ toolchain += "windows"
+ import platform as pltfm
+ if pltfm.machine().endswith("64"):
+ toolchain += "-x86_64"
+ elif host_platform == "linux":
+ toolchain += "linux-x86_64"
+ elif host_platform == "osx":
+ toolchain += "darwin-x86_64"
+ env.PrependENVPath('PATH', toolchain + "/bin") # This does nothing half of the time, but we'll put it here anyways
+
+ # Get architecture info
+ arch_info_table = {
+ "armv7" : {
+ "march":"armv7-a", "target":"armv7a-linux-androideabi", "tool_path":"arm-linux-androideabi", "compiler_path":"armv7a-linux-androideabi",
+ "ccflags" : ['-mfpu=neon']
+ },
+ "arm64v8" : {
+ "march":"armv8-a", "target":"aarch64-linux-android", "tool_path":"aarch64-linux-android", "compiler_path":"aarch64-linux-android",
+ "ccflags" : []
+ },
+ "x86" : {
+ "march":"i686", "target":"i686-linux-android", "tool_path":"i686-linux-android", "compiler_path":"i686-linux-android",
+ "ccflags" : ['-mstackrealign']
+ },
+ "x86_64" : {"march":"x86-64", "target":"x86_64-linux-android", "tool_path":"x86_64-linux-android", "compiler_path":"x86_64-linux-android",
+ "ccflags" : []
+ }
+ }
+ arch_info = arch_info_table[env['android_arch']]
+
+ # Setup tools
+ env['CC'] = toolchain + "/bin/clang"
+ env['CXX'] = toolchain + "/bin/clang++"
+ env['AR'] = toolchain + "/bin/" + arch_info['tool_path'] + "-ar"
+
+ env.Append(CCFLAGS=['--target=' + arch_info['target'] + env['android_api_level'], '-march=' + arch_info['march'], '-fPIC'])#, '-fPIE', '-fno-addrsig', '-Oz'])
+ env.Append(CCFLAGS=arch_info['ccflags'])
+
+arch_suffix = env['bits']
+if env['platform'] == 'android':
+ arch_suffix = env['android_arch']
+if env['platform'] == 'ios':
+ arch_suffix = env['ios_arch']
+
+env.Append(CPPPATH=[
+ '.',
+ env['headers_dir'],
+ 'godot_remote',
+ 'godot-cpp/include',
+ 'godot-cpp/include/gen',
+ 'godot-cpp/include/core',
+])
+
+env.Append(LIBPATH=['godot-cpp/bin/'])
+env.Append(LIBS=[
+ 'libgodot-cpp.{}.{}.{}{}'.format( # godot-cpp lib
+ env['platform'],
+ env['target'],
+ arch_suffix,
+ env['LIBSUFFIX']),
+])
+
+# Generate bindings?
+json_api_file = ''
+
+if 'custom_api_file' in env:
+ json_api_file = env['custom_api_file']
+else:
+ json_api_file = os.path.join(os.getcwd(), env['headers_dir'], 'api.json')
+
+if env['generate_bindings'] == 'auto':
+ # Check if generated files exist
+ should_generate_bindings = not os.path.isfile(os.path.join(os.getcwd(), 'src', 'gen', 'Object.cpp'))
+else:
+ should_generate_bindings = env['generate_bindings'] in ['yes', 'true']
+
+env.Append(CPPDEFINES=['GDNATIVE_LIBRARY'])
+
+if env['godot_remote_no_default_resources'] == True:
+ env.Append(CPPDEFINES=['NO_GODOTREMOTE_DEFAULT_RESOURCES'])
+
+if env['godot_remote_disable_server'] == True:
+ env.Append(CPPDEFINES=['NO_GODOTREMOTE_SERVER'])
+
+if env['godot_remote_disable_client'] == True:
+ env.Append(CPPDEFINES=['NO_GODOTREMOTE_CLIENT'])
+
+# Sources to compile
+sources = []
+add_sources(sources, 'godot_remote', 'cpp')
+
+library = env.SharedLibrary(
+ target='bin/' + 'godot_remote.{}.{}.{}{}'.format(
+ env['platform'],
+ env['target'],
+ arch_suffix,
+ env['SHLIBSUFFIX']
+ #env['LIBSUFFIX']
+ ), source=sources
+)
+Default(library)
diff --git a/modules/godot_remote/build_android_gdn.bat b/modules/godot_remote/build_android_gdn.bat
new file mode 100644
index 0000000..b3ff921
--- /dev/null
+++ b/modules/godot_remote/build_android_gdn.bat
@@ -0,0 +1 @@
+%ANDROID_NDK_ROOT%/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk APP_PLATFORM=android-21 -j8
\ No newline at end of file
diff --git a/modules/godot_remote/build_godot_cpp.bat b/modules/godot_remote/build_godot_cpp.bat
new file mode 100644
index 0000000..b959792
--- /dev/null
+++ b/modules/godot_remote/build_godot_cpp.bat
@@ -0,0 +1,14 @@
+cd godot-cpp
+set cpu=%NUMBER_OF_PROCESSORS%
+set api=custom_api_file="../api.json"
+
+scons generate_bindings=true platform=windows target=release bits=64 -j%cpu% %api%
+scons platform=windows target=release bits=64 -j%cpu% %api%
+scons platform=windows target=debug bits=64 -j%cpu% %api%
+
+scons platform=android target=debug android_arch=arm64v8 -j%cpu% %api%
+
+scons platform=android target=release android_arch=arm64v8 -j%cpu% %api%
+scons platform=android target=release android_arch=armv7 -j%cpu% %api%
+scons platform=android target=release android_arch=x86 -j%cpu% %api%
+scons platform=android target=release android_arch=x86_64 -j%cpu% %api%
\ No newline at end of file
diff --git a/modules/godot_remote/build_windows.bat b/modules/godot_remote/build_windows.bat
new file mode 100644
index 0000000..d1c8b33
--- /dev/null
+++ b/modules/godot_remote/build_windows.bat
@@ -0,0 +1 @@
+scons platform=windows bits=64 target=release -j8
\ No newline at end of file
diff --git a/modules/godot_remote/examples/custom_input_scene/CustomInput.gd b/modules/godot_remote/examples/custom_input_scene/CustomInput.gd
new file mode 100644
index 0000000..846f841
--- /dev/null
+++ b/modules/godot_remote/examples/custom_input_scene/CustomInput.gd
@@ -0,0 +1,10 @@
+extends Control
+
+# If you want to disable capturing touch events or mouse pointer
+# you can use this method
+func _enter_tree():
+ GodotRemote.get_device().set_capture_pointer(false)
+
+# And after removing this scene you need to restore value
+func _exit_tree():
+ GodotRemote.get_device().set_capture_pointer(true)
diff --git a/modules/godot_remote/examples/custom_input_scene/CustomInput.tscn b/modules/godot_remote/examples/custom_input_scene/CustomInput.tscn
new file mode 100644
index 0000000..9b8d726
--- /dev/null
+++ b/modules/godot_remote/examples/custom_input_scene/CustomInput.tscn
@@ -0,0 +1,40 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://CustomInput.gd" type="Script" id=1]
+[ext_resource path="res://icon.png" type="Texture" id=2]
+
+[node name="CustomInput" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Control" type="Control" parent="."]
+anchor_top = 0.5
+anchor_bottom = 0.5
+margin_top = -20.0
+margin_right = 40.0
+margin_bottom = 20.0
+
+[node name="TouchScreenButton" type="TouchScreenButton" parent="Control"]
+position = Vector2( 33, -180 )
+scale = Vector2( 5.45313, 6.5 )
+normal = ExtResource( 2 )
+action = "ui_left"
+
+[node name="Control2" type="Control" parent="."]
+anchor_left = 1.0
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+margin_left = -40.0
+margin_top = -20.0
+margin_bottom = 20.0
+
+[node name="TouchScreenButton2" type="TouchScreenButton" parent="Control2"]
+position = Vector2( -342.74, -180 )
+scale = Vector2( 5.45313, 6.5 )
+normal = ExtResource( 2 )
+action = "ui_right"
diff --git a/modules/godot_remote/examples/custom_input_scene/Main.tscn b/modules/godot_remote/examples/custom_input_scene/Main.tscn
new file mode 100644
index 0000000..ec12619
--- /dev/null
+++ b/modules/godot_remote/examples/custom_input_scene/Main.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://Player.gd" type="Script" id=2]
+[ext_resource path="res://icon.png" type="Texture" id=3]
+
+[sub_resource type="RectangleShape2D" id=1]
+extents = Vector2( 32.04, 30.9 )
+
+[sub_resource type="RectangleShape2D" id=2]
+extents = Vector2( 32, 32 )
+
+[node name="Main" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Ground" type="StaticBody2D" parent="."]
+position = Vector2( 522, 493.5 )
+scale = Vector2( 17.1563, 0.265625 )
+
+[node name="Sprite" type="Sprite" parent="Ground"]
+texture = ExtResource( 3 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Ground"]
+shape = SubResource( 1 )
+
+[node name="Player" type="KinematicBody2D" parent="."]
+position = Vector2( 527.072, 375.837 )
+script = ExtResource( 2 )
+
+[node name="Sprite" type="Sprite" parent="Player"]
+texture = ExtResource( 3 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Player"]
+shape = SubResource( 2 )
diff --git a/modules/godot_remote/examples/custom_input_scene/Player.gd b/modules/godot_remote/examples/custom_input_scene/Player.gd
new file mode 100644
index 0000000..d278a1d
--- /dev/null
+++ b/modules/godot_remote/examples/custom_input_scene/Player.gd
@@ -0,0 +1,15 @@
+extends KinematicBody2D
+
+var speed = 500.0
+
+# Basic player logic
+func _physics_process(delta):
+ var direction = Vector2()
+
+ if Input.is_action_pressed("ui_left"):
+ direction.x -= 1
+ if Input.is_action_pressed("ui_right"):
+ direction.x += 1
+
+ var velocity = (direction * speed + Vector2.DOWN * 98)
+ move_and_slide(velocity, Vector2.UP)
diff --git a/modules/godot_remote/examples/custom_input_scene/default_env.tres b/modules/godot_remote/examples/custom_input_scene/default_env.tres
new file mode 100644
index 0000000..20207a4
--- /dev/null
+++ b/modules/godot_remote/examples/custom_input_scene/default_env.tres
@@ -0,0 +1,7 @@
+[gd_resource type="Environment" load_steps=2 format=2]
+
+[sub_resource type="ProceduralSky" id=1]
+
+[resource]
+background_mode = 2
+background_sky = SubResource( 1 )
diff --git a/modules/godot_remote/examples/custom_input_scene/icon.png b/modules/godot_remote/examples/custom_input_scene/icon.png
new file mode 100644
index 0000000..0af4d53
Binary files /dev/null and b/modules/godot_remote/examples/custom_input_scene/icon.png differ
diff --git a/modules/godot_remote/examples/custom_input_scene/icon.png.import b/modules/godot_remote/examples/custom_input_scene/icon.png.import
new file mode 100644
index 0000000..96cbf46
--- /dev/null
+++ b/modules/godot_remote/examples/custom_input_scene/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.png"
+dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/examples/custom_input_scene/project.godot b/modules/godot_remote/examples/custom_input_scene/project.godot
new file mode 100644
index 0000000..77ed7c1
--- /dev/null
+++ b/modules/godot_remote/examples/custom_input_scene/project.godot
@@ -0,0 +1,49 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=4
+
+_global_script_classes=[ ]
+_global_script_class_icons={
+
+}
+
+[application]
+
+config/name="Custom Input Scene"
+run/main_scene="res://Main.tscn"
+config/icon="res://icon.png"
+
+[debug]
+
+godot_remote/server_custom_input_scene/custom_input_scene="res://CustomInput.tscn"
+godot_remote/server/video_stream_enabled=false
+
+[input]
+
+ui_left={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
+ ]
+}
+ui_right={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
+ ]
+}
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+vram_compression/import_etc=true
+vram_compression/import_etc2=false
+environment/default_environment="res://default_env.tres"
diff --git a/modules/godot_remote/examples/custom_user_packets/Control.gd b/modules/godot_remote/examples/custom_user_packets/Control.gd
new file mode 100644
index 0000000..ccab0f7
--- /dev/null
+++ b/modules/godot_remote/examples/custom_user_packets/Control.gd
@@ -0,0 +1,16 @@
+extends Control
+
+func _ready() -> void:
+ GodotRemote.connect("device_added", self, "device_added")
+
+func device_added():
+ print(GodotRemote.get_device().get_custom_input_scene())
+ GodotRemote.get_device().connect("user_data_received", self, "user_data_received")
+
+func user_data_received(packet_id, user_data):
+ print("Received packet: %s, data: %s" % [packet_id, user_data])
+ match packet_id:
+ "bg_color": VisualServer.set_default_clear_color(user_data)
+
+func _on_HSlider_value_changed(value: float) -> void:
+ GodotRemote.get_device().send_user_data("slider_value", value, false)
diff --git a/modules/godot_remote/examples/custom_user_packets/Control.tscn b/modules/godot_remote/examples/custom_user_packets/Control.tscn
new file mode 100644
index 0000000..f9078e8
--- /dev/null
+++ b/modules/godot_remote/examples/custom_user_packets/Control.tscn
@@ -0,0 +1,74 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://Control.gd" type="Script" id=1]
+[ext_resource path="res://icon.png" type="Texture" id=2]
+
+[sub_resource type="Animation" id=1]
+resource_name = "New Anim"
+length = 3.0
+loop = true
+tracks/0/type = "value"
+tracks/0/path = NodePath("Control:rect_position")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/keys = {
+"times": PoolRealArray( 0, 0.9, 2.2 ),
+"transitions": PoolRealArray( 1, 1, 1 ),
+"update": 0,
+"values": [ Vector2( 730, 32 ), Vector2( 815.526, 473.886 ), Vector2( 203.097, 448.007 ) ]
+}
+tracks/1/type = "value"
+tracks/1/path = NodePath("Control:rect_rotation")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/keys = {
+"times": PoolRealArray( 0, 0.9, 2.2 ),
+"transitions": PoolRealArray( 1, 1, 1 ),
+"update": 0,
+"values": [ 0.0, 422.2, -251.5 ]
+}
+
+[node name="Control" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HSlider" type="HSlider" parent="."]
+margin_left = 9.0
+margin_top = 129.0
+margin_right = 409.0
+margin_bottom = 162.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Label" type="Label" parent="."]
+margin_left = 15.0
+margin_top = 103.0
+margin_right = 334.0
+margin_bottom = 125.0
+text = "Remote progress bar value"
+
+[node name="Control" type="TextureRect" parent="."]
+margin_left = 454.735
+margin_top = 458.64
+margin_right = 518.738
+margin_bottom = 522.641
+rect_rotation = 25.3133
+texture = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+autoplay = "New Anim"
+"anims/New Anim" = SubResource( 1 )
+
+[connection signal="value_changed" from="HSlider" to="." method="_on_HSlider_value_changed"]
diff --git a/modules/godot_remote/examples/custom_user_packets/ControlColorRemote.gd b/modules/godot_remote/examples/custom_user_packets/ControlColorRemote.gd
new file mode 100644
index 0000000..d50a91d
--- /dev/null
+++ b/modules/godot_remote/examples/custom_user_packets/ControlColorRemote.gd
@@ -0,0 +1,12 @@
+extends Control
+
+func _ready() -> void:
+ GodotRemote.get_device().connect("user_data_received", self, "user_data_received")
+
+func _on_ColorPickerButton_color_changed(color: Color) -> void:
+ GodotRemote.get_device().send_user_data("bg_color", color, false)
+
+func user_data_received(packet_id, user_data):
+ print("Received packet: %s, data: %s" % [packet_id, user_data])
+ match packet_id:
+ "slider_value": $ProgressBar.value = user_data
diff --git a/modules/godot_remote/examples/custom_user_packets/ControlColorRemote.tscn b/modules/godot_remote/examples/custom_user_packets/ControlColorRemote.tscn
new file mode 100644
index 0000000..297b7aa
--- /dev/null
+++ b/modules/godot_remote/examples/custom_user_packets/ControlColorRemote.tscn
@@ -0,0 +1,61 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://ControlColorRemote.gd" type="Script" id=1]
+
+[node name="Control" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_lock_": true,
+"_edit_use_anchors_": false
+}
+
+[node name="ProgressBar" type="ProgressBar" parent="."]
+anchor_left = 0.0556641
+anchor_top = 0.0383333
+anchor_right = 0.959961
+anchor_bottom = 0.0883333
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="Control" type="Control" parent="."]
+anchor_left = 0.319902
+anchor_top = 0.262483
+anchor_right = 0.702714
+anchor_bottom = 0.945817
+margin_bottom = -3.8147e-06
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Label" type="Label" parent="Control/VBoxContainer"]
+margin_top = 45.0
+margin_right = 391.0
+margin_bottom = 59.0
+size_flags_vertical = 6
+size_flags_stretch_ratio = 0.35
+text = "SELECT BACKGROUND COLOR"
+align = 1
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="ColorPickerButton" type="ColorPickerButton" parent="Control/VBoxContainer"]
+margin_top = 109.0
+margin_right = 391.0
+margin_bottom = 410.0
+size_flags_vertical = 3
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[connection signal="color_changed" from="Control/VBoxContainer/ColorPickerButton" to="." method="_on_ColorPickerButton_color_changed"]
diff --git a/modules/godot_remote/examples/custom_user_packets/default_env.tres b/modules/godot_remote/examples/custom_user_packets/default_env.tres
new file mode 100644
index 0000000..20207a4
--- /dev/null
+++ b/modules/godot_remote/examples/custom_user_packets/default_env.tres
@@ -0,0 +1,7 @@
+[gd_resource type="Environment" load_steps=2 format=2]
+
+[sub_resource type="ProceduralSky" id=1]
+
+[resource]
+background_mode = 2
+background_sky = SubResource( 1 )
diff --git a/modules/godot_remote/examples/custom_user_packets/icon.png b/modules/godot_remote/examples/custom_user_packets/icon.png
new file mode 100644
index 0000000..1128fca
Binary files /dev/null and b/modules/godot_remote/examples/custom_user_packets/icon.png differ
diff --git a/modules/godot_remote/examples/custom_user_packets/icon.png.import b/modules/godot_remote/examples/custom_user_packets/icon.png.import
new file mode 100644
index 0000000..9725e52
--- /dev/null
+++ b/modules/godot_remote/examples/custom_user_packets/icon.png.import
@@ -0,0 +1,36 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path.s3tc="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.s3tc.stex"
+path.etc="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.etc.stex"
+metadata={
+"imported_formats": [ "s3tc", "etc" ],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://icon.png"
+dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.s3tc.stex", "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.etc.stex" ]
+
+[params]
+
+compress/mode=2
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=true
+flags/filter=true
+flags/mipmaps=true
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/modules/godot_remote/examples/custom_user_packets/project.godot b/modules/godot_remote/examples/custom_user_packets/project.godot
new file mode 100644
index 0000000..61f760c
--- /dev/null
+++ b/modules/godot_remote/examples/custom_user_packets/project.godot
@@ -0,0 +1,26 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=4
+
+[application]
+
+config/name="Custom User Packets"
+run/main_scene="res://Control.tscn"
+config/icon="res://icon.png"
+
+[debug]
+
+godot_remote/server_custom_input_scene/custom_input_scene="res://ControlColorRemote.tscn"
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+vram_compression/import_etc=true
+vram_compression/import_etc2=false
+environment/default_environment="res://default_env.tres"
diff --git a/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/ControlToShowStream.gd b/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/ControlToShowStream.gd
new file mode 100644
index 0000000..3ceeb5a
--- /dev/null
+++ b/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/ControlToShowStream.gd
@@ -0,0 +1,32 @@
+extends Control
+
+
+# firstly you need to disable autostart GodotRemote in Project Settings/Debug/Godot Remote/General
+# and change the Network/Limits/Connect Timeout Seconds to 1 otherwise app will be closing very long time
+func _ready():
+ # create client
+ GodotRemote.create_remote_device(GodotRemote.DEVICE_CLIENT)
+
+ # get device and convert it to client class
+ var d : GRClient = GodotRemote.get_device()
+ # set control where you want to see stream. it can be whole screen control or custom 'viewport'
+ d.set_control_to_show_in(self)
+ # set address of server. optional if you want to connect to other projects on one pc or if you use connection over adb
+ d.set_address("127.0.0.1")
+ # set password to get acces to the server if it need one
+ d.password = "1234"
+ # and change other settings if you need it
+
+ # start client
+ GodotRemote.start_remote_device()
+
+# If you need to support custom input scenes best way to avoid any errors by overriding resources
+# from server is just put all assets of this project to folder with unique and long name
+#
+# Example:
+# *res://
+# -UniqueL0ngNameThatINeverOverrideFromServer
+# -icon.png
+# -default_env.tres
+# -Scene.tscn
+# -Scene.gd
diff --git a/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/ControlToShowStream.tscn b/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/ControlToShowStream.tscn
new file mode 100644
index 0000000..b117538
--- /dev/null
+++ b/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/ControlToShowStream.tscn
@@ -0,0 +1,11 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://UniqueL0ngNameThatINeverOverrideFromServer/ControlToShowStream.gd" type="Script" id=1]
+
+[node name="ControlToShowStreamIn" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
diff --git a/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/icon.png b/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/icon.png
new file mode 100644
index 0000000..35be6c8
Binary files /dev/null and b/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/icon.png differ
diff --git a/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/icon.png.import b/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/icon.png.import
new file mode 100644
index 0000000..899cebf
--- /dev/null
+++ b/modules/godot_remote/examples/simple_client/UniqueL0ngNameThatINeverOverrideFromServer/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/icon.png-9b69519d77c698a6f97b5d1c3f5e548f.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://UniqueL0ngNameThatINeverOverrideFromServer/icon.png"
+dest_files=[ "res://.import/icon.png-9b69519d77c698a6f97b5d1c3f5e548f.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/examples/simple_client/project.godot b/modules/godot_remote/examples/simple_client/project.godot
new file mode 100644
index 0000000..4f6193b
--- /dev/null
+++ b/modules/godot_remote/examples/simple_client/project.godot
@@ -0,0 +1,34 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=4
+
+[application]
+
+config/name="Example Client"
+run/main_scene="res://UniqueL0ngNameThatINeverOverrideFromServer/ControlToShowStream.tscn"
+config/icon="res://UniqueL0ngNameThatINeverOverrideFromServer/icon.png"
+
+[debug]
+
+settings/stdout/verbose_stdout=true
+godot_remote/general/autostart=false
+
+[editor_plugins]
+
+enabled=PoolStringArray( )
+
+[network]
+
+limits/tcp/connect_timeout_seconds=1
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+vram_compression/import_etc=true
+vram_compression/import_etc2=false
diff --git a/modules/godot_remote/examples/viewport_size_syncing/Main.gd b/modules/godot_remote/examples/viewport_size_syncing/Main.gd
new file mode 100644
index 0000000..cb04a37
--- /dev/null
+++ b/modules/godot_remote/examples/viewport_size_syncing/Main.gd
@@ -0,0 +1,23 @@
+extends Control
+
+var is_vertical = false
+var screen_aspect = OS.window_size.x / OS.window_size.y
+
+func _ready():
+ # Waiting for one frame until the device is created
+ yield(get_tree(), "idle_frame")
+ # Connect to server signals
+ GodotRemote.get_device().connect("client_viewport_orientation_changed", self, "_screen_rotated")
+ GodotRemote.get_device().connect("client_viewport_aspect_ratio_changed", self, "_screen_aspect_changed")
+
+# Simple functions to resize window
+func _screen_rotated(_is_vertical):
+ is_vertical = _is_vertical
+ if _is_vertical:
+ OS.window_size = Vector2(600, 600 / screen_aspect)
+ else:
+ OS.window_size = Vector2(600 * screen_aspect, 600)
+
+func _screen_aspect_changed(_aspect):
+ screen_aspect = _aspect
+ _screen_rotated(is_vertical)
diff --git a/modules/godot_remote/examples/viewport_size_syncing/Main.tscn b/modules/godot_remote/examples/viewport_size_syncing/Main.tscn
new file mode 100644
index 0000000..41213b4
--- /dev/null
+++ b/modules/godot_remote/examples/viewport_size_syncing/Main.tscn
@@ -0,0 +1,15 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://icon.png" type="Texture" id=1]
+[ext_resource path="res://Main.gd" type="Script" id=2]
+
+[node name="Main" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 2 )
+
+[node name="TextureRect" type="TextureRect" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+texture = ExtResource( 1 )
+stretch_mode = 1
diff --git a/modules/godot_remote/examples/viewport_size_syncing/default_env.tres b/modules/godot_remote/examples/viewport_size_syncing/default_env.tres
new file mode 100644
index 0000000..20207a4
--- /dev/null
+++ b/modules/godot_remote/examples/viewport_size_syncing/default_env.tres
@@ -0,0 +1,7 @@
+[gd_resource type="Environment" load_steps=2 format=2]
+
+[sub_resource type="ProceduralSky" id=1]
+
+[resource]
+background_mode = 2
+background_sky = SubResource( 1 )
diff --git a/modules/godot_remote/examples/viewport_size_syncing/icon.png b/modules/godot_remote/examples/viewport_size_syncing/icon.png
new file mode 100644
index 0000000..6117566
Binary files /dev/null and b/modules/godot_remote/examples/viewport_size_syncing/icon.png differ
diff --git a/modules/godot_remote/examples/viewport_size_syncing/icon.png.import b/modules/godot_remote/examples/viewport_size_syncing/icon.png.import
new file mode 100644
index 0000000..96cbf46
--- /dev/null
+++ b/modules/godot_remote/examples/viewport_size_syncing/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.png"
+dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/modules/godot_remote/examples/viewport_size_syncing/project.godot b/modules/godot_remote/examples/viewport_size_syncing/project.godot
new file mode 100644
index 0000000..4edafa0
--- /dev/null
+++ b/modules/godot_remote/examples/viewport_size_syncing/project.godot
@@ -0,0 +1,26 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=4
+
+[application]
+
+config/name="Viewport Size Syncing"
+run/main_scene="res://Main.tscn"
+config/icon="res://icon.png"
+
+[display]
+
+window/size/width=600
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+vram_compression/import_etc=true
+vram_compression/import_etc2=false
+environment/default_environment="res://default_env.tres"
diff --git a/modules/godot_remote/godot_remote/GRClient.cpp b/modules/godot_remote/godot_remote/GRClient.cpp
new file mode 100644
index 0000000..f1ec16d
--- /dev/null
+++ b/modules/godot_remote/godot_remote/GRClient.cpp
@@ -0,0 +1,2001 @@
+/* GRClient.cpp */
+
+#ifndef NO_GODOTREMOTE_CLIENT
+
+#include "GRClient.h"
+#include "GRNotifications.h"
+#include "GRPacket.h"
+#include "GRResources.h"
+#include "GodotRemote.h"
+
+#ifndef GDNATIVE_LIBRARY
+
+#include "core/input_map.h"
+#include "core/io/file_access_pack.h"
+#include "core/io/ip.h"
+#include "core/io/resource_loader.h"
+#include "core/io/tcp_server.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "core/os/input_event.h"
+#include "core/os/thread_safe.h"
+#include "main/input_default.h"
+#include "scene/gui/control.h"
+#include "scene/main/node.h"
+#include "scene/main/scene_tree.h"
+#include "scene/main/viewport.h"
+#include "scene/resources/material.h"
+#include "scene/resources/packed_scene.h"
+#include "scene/resources/texture.h"
+
+#else
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+using namespace godot;
+
+#define BUTTON_WHEEL_UP GlobalConstants::BUTTON_WHEEL_UP
+#define BUTTON_WHEEL_DOWN GlobalConstants::BUTTON_WHEEL_DOWN
+#define BUTTON_WHEEL_LEFT GlobalConstants::BUTTON_WHEEL_LEFT
+#define BUTTON_WHEEL_RIGHT GlobalConstants::BUTTON_WHEEL_RIGHT
+#endif
+
+enum class DeletingVarName {
+ CONTROL_TO_SHOW_STREAM,
+ TEXTURE_TO_SHOW_STREAM,
+ INPUT_COLLECTOR,
+ CUSTOM_INPUT_SCENE,
+};
+
+using namespace GRUtils;
+
+#ifndef GDNATIVE_LIBRARY
+void GRClient::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("_update_texture_from_image", "image"), &GRClient::_update_texture_from_image);
+ ClassDB::bind_method(D_METHOD("_update_stream_texture_state", "state"), &GRClient::_update_stream_texture_state);
+ ClassDB::bind_method(D_METHOD("_force_update_stream_viewport_signals"), &GRClient::_force_update_stream_viewport_signals);
+ ClassDB::bind_method(D_METHOD("_viewport_size_changed"), &GRClient::_viewport_size_changed);
+ ClassDB::bind_method(D_METHOD("_load_custom_input_scene", "_data"), &GRClient::_load_custom_input_scene);
+ ClassDB::bind_method(D_METHOD("_remove_custom_input_scene"), &GRClient::_remove_custom_input_scene);
+ ClassDB::bind_method(D_METHOD("_on_node_deleting", "var_name"), &GRClient::_on_node_deleting);
+
+ ClassDB::bind_method(D_METHOD("set_control_to_show_in", "control_node", "position_in_node"), &GRClient::set_control_to_show_in, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("set_custom_no_signal_texture", "texture"), &GRClient::set_custom_no_signal_texture);
+ ClassDB::bind_method(D_METHOD("set_custom_no_signal_vertical_texture", "texture"), &GRClient::set_custom_no_signal_vertical_texture);
+ ClassDB::bind_method(D_METHOD("set_custom_no_signal_material", "material"), &GRClient::set_custom_no_signal_material);
+ ClassDB::bind_method(D_METHOD("set_address_port", "ip", "port"), &GRClient::set_address_port);
+ ClassDB::bind_method(D_METHOD("set_address", "ip"), &GRClient::set_address);
+ ClassDB::bind_method(D_METHOD("set_server_setting", "setting", "value"), &GRClient::set_server_setting);
+ ClassDB::bind_method(D_METHOD("disable_overriding_server_settings"), &GRClient::disable_overriding_server_settings);
+
+ ClassDB::bind_method(D_METHOD("get_custom_input_scene"), &GRClient::get_custom_input_scene);
+ ClassDB::bind_method(D_METHOD("get_address"), &GRClient::get_address);
+ ClassDB::bind_method(D_METHOD("is_stream_active"), &GRClient::is_stream_active);
+ ClassDB::bind_method(D_METHOD("is_connected_to_host"), &GRClient::is_connected_to_host);
+
+ ADD_SIGNAL(MethodInfo("custom_input_scene_added"));
+ ADD_SIGNAL(MethodInfo("custom_input_scene_removed"));
+
+ ADD_SIGNAL(MethodInfo("stream_state_changed", PropertyInfo(Variant::INT, "state", PROPERTY_HINT_ENUM)));
+ ADD_SIGNAL(MethodInfo("connection_state_changed", PropertyInfo(Variant::BOOL, "is_connected")));
+ ADD_SIGNAL(MethodInfo("mouse_mode_changed", PropertyInfo(Variant::INT, "mouse_mode")));
+ ADD_SIGNAL(MethodInfo("server_settings_received", PropertyInfo(Variant::DICTIONARY, "settings")));
+
+ // SETGET
+ ClassDB::bind_method(D_METHOD("set_capture_on_focus", "val"), &GRClient::set_capture_on_focus);
+ ClassDB::bind_method(D_METHOD("set_capture_when_hover", "val"), &GRClient::set_capture_when_hover);
+ ClassDB::bind_method(D_METHOD("set_capture_pointer", "val"), &GRClient::set_capture_pointer);
+ ClassDB::bind_method(D_METHOD("set_capture_input", "val"), &GRClient::set_capture_input);
+ ClassDB::bind_method(D_METHOD("set_connection_type", "type"), &GRClient::set_connection_type);
+ ClassDB::bind_method(D_METHOD("set_target_send_fps", "fps"), &GRClient::set_target_send_fps);
+ ClassDB::bind_method(D_METHOD("set_stretch_mode", "mode"), &GRClient::set_stretch_mode);
+ ClassDB::bind_method(D_METHOD("set_texture_filtering", "is_filtered"), &GRClient::set_texture_filtering);
+ ClassDB::bind_method(D_METHOD("set_password", "password"), &GRClient::set_password);
+ ClassDB::bind_method(D_METHOD("set_device_id", "id"), &GRClient::set_device_id);
+ ClassDB::bind_method(D_METHOD("set_viewport_orientation_syncing", "is_syncing"), &GRClient::set_viewport_orientation_syncing);
+ ClassDB::bind_method(D_METHOD("set_viewport_aspect_ratio_syncing", "is_syncing"), &GRClient::set_viewport_aspect_ratio_syncing);
+ ClassDB::bind_method(D_METHOD("set_server_settings_syncing", "is_syncing"), &GRClient::set_server_settings_syncing);
+
+ ClassDB::bind_method(D_METHOD("is_capture_on_focus"), &GRClient::is_capture_on_focus);
+ ClassDB::bind_method(D_METHOD("is_capture_when_hover"), &GRClient::is_capture_when_hover);
+ ClassDB::bind_method(D_METHOD("is_capture_pointer"), &GRClient::is_capture_pointer);
+ ClassDB::bind_method(D_METHOD("is_capture_input"), &GRClient::is_capture_input);
+ ClassDB::bind_method(D_METHOD("get_connection_type"), &GRClient::get_connection_type);
+ ClassDB::bind_method(D_METHOD("get_target_send_fps"), &GRClient::get_target_send_fps);
+ ClassDB::bind_method(D_METHOD("get_stretch_mode"), &GRClient::get_stretch_mode);
+ ClassDB::bind_method(D_METHOD("get_texture_filtering"), &GRClient::get_texture_filtering);
+ ClassDB::bind_method(D_METHOD("get_password"), &GRClient::get_password);
+ ClassDB::bind_method(D_METHOD("get_device_id"), &GRClient::get_device_id);
+ ClassDB::bind_method(D_METHOD("is_viewport_orientation_syncing"), &GRClient::is_viewport_orientation_syncing);
+ ClassDB::bind_method(D_METHOD("is_viewport_aspect_ratio_syncing"), &GRClient::is_viewport_aspect_ratio_syncing);
+ ClassDB::bind_method(D_METHOD("is_server_settings_syncing"), &GRClient::is_server_settings_syncing);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_on_focus"), "set_capture_on_focus", "is_capture_on_focus");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_when_hover"), "set_capture_when_hover", "is_capture_when_hover");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_pointer"), "set_capture_pointer", "is_capture_pointer");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_input"), "set_capture_input", "is_capture_input");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "connection_type", PROPERTY_HINT_ENUM, "WiFi,ADB"), "set_connection_type", "get_connection_type");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "target_send_fps", PROPERTY_HINT_RANGE, "1,1000"), "set_target_send_fps", "get_target_send_fps");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Fill,Keep Aspect"), "set_stretch_mode", "get_stretch_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "texture_filtering"), "set_texture_filtering", "get_texture_filtering");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "password"), "set_password", "get_password");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "device_id"), "set_device_id", "get_device_id");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "viewport_orientation_syncing"), "set_viewport_orientation_syncing", "is_viewport_orientation_syncing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "viewport_aspect_ratio_syncing"), "set_viewport_aspect_ratio_syncing", "is_viewport_aspect_ratio_syncing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_settings_syncing"), "set_server_settings_syncing", "is_server_settings_syncing");
+
+ BIND_ENUM_CONSTANT(CONNECTION_ADB);
+ BIND_ENUM_CONSTANT(CONNECTION_WiFi);
+
+ BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT);
+ BIND_ENUM_CONSTANT(STRETCH_FILL);
+
+ BIND_ENUM_CONSTANT(STREAM_NO_SIGNAL);
+ BIND_ENUM_CONSTANT(STREAM_ACTIVE);
+ BIND_ENUM_CONSTANT(STREAM_NO_IMAGE);
+}
+
+#else
+
+void GRClient::_register_methods() {
+ ///////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////
+ /*
+ METHOD_REG(GRClient::_internal_call_only_deffered_start);
+ METHOD_REG(GRClient::_internal_call_only_deffered_stop);
+
+ METHOD_REG(GRClient::_internal_call_only_deffered_restart);
+
+ METHOD_REG(GRClient::get_avg_ping);
+ METHOD_REG(GRClient::get_avg_fps);
+
+ METHOD_REG(GRClient::get_port);
+ METHOD_REG(GRClient::set_port);
+
+ METHOD_REG(GRClient::start);
+ METHOD_REG(GRClient::stop);
+ METHOD_REG(GRClient::get_status);
+
+ register_signal("status_changed", "status", GODOT_VARIANT_TYPE_INT);
+ register_property("port", &GRClient::set_port, &GRClient::get_port, 52341);
+ */
+ ///////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////
+
+ METHOD_REG(GRClient, _notification);
+ METHOD_REG(GRClient, _thread_connection);
+ METHOD_REG(GRClient, _thread_image_decoder);
+
+ METHOD_REG(GRClient, _update_texture_from_image);
+ METHOD_REG(GRClient, _update_stream_texture_state);
+ METHOD_REG(GRClient, _force_update_stream_viewport_signals);
+ METHOD_REG(GRClient, _viewport_size_changed);
+ METHOD_REG(GRClient, _load_custom_input_scene);
+ METHOD_REG(GRClient, _remove_custom_input_scene);
+ METHOD_REG(GRClient, _on_node_deleting);
+
+ METHOD_REG(GRClient, set_control_to_show_in);
+ METHOD_REG(GRClient, set_custom_no_signal_texture);
+ METHOD_REG(GRClient, set_custom_no_signal_vertical_texture);
+ METHOD_REG(GRClient, set_custom_no_signal_material);
+ METHOD_REG(GRClient, set_address_port);
+ METHOD_REG(GRClient, set_address);
+ METHOD_REG(GRClient, set_server_setting);
+ METHOD_REG(GRClient, disable_overriding_server_settings);
+
+ METHOD_REG(GRClient, get_custom_input_scene);
+ METHOD_REG(GRClient, get_address);
+ METHOD_REG(GRClient, is_stream_active);
+ METHOD_REG(GRClient, is_connected_to_host);
+
+ register_signal("custom_input_scene_added", Dictionary::make());
+ register_signal("custom_input_scene_removed", Dictionary::make());
+
+ register_signal("stream_state_changed", "state", GODOT_VARIANT_TYPE_INT);
+ register_signal("connection_state_changed", "is_connected", GODOT_VARIANT_TYPE_BOOL);
+ register_signal("mouse_mode_changed", "mouse_mode", GODOT_VARIANT_TYPE_INT);
+ register_signal("server_settings_received", "settings", GODOT_VARIANT_TYPE_DICTIONARY);
+
+ // SETGET
+ METHOD_REG(GRClient, set_capture_on_focus);
+ METHOD_REG(GRClient, set_capture_when_hover);
+ METHOD_REG(GRClient, set_capture_pointer);
+ METHOD_REG(GRClient, set_capture_input);
+ METHOD_REG(GRClient, set_connection_type);
+ METHOD_REG(GRClient, set_target_send_fps);
+ METHOD_REG(GRClient, set_stretch_mode);
+ METHOD_REG(GRClient, set_texture_filtering);
+ METHOD_REG(GRClient, set_password);
+ METHOD_REG(GRClient, set_device_id);
+ METHOD_REG(GRClient, set_viewport_orientation_syncing);
+ METHOD_REG(GRClient, set_viewport_aspect_ratio_syncing);
+ METHOD_REG(GRClient, set_server_settings_syncing);
+
+ METHOD_REG(GRClient, is_capture_on_focus);
+ METHOD_REG(GRClient, is_capture_when_hover);
+ METHOD_REG(GRClient, is_capture_pointer);
+ METHOD_REG(GRClient, is_capture_input);
+ METHOD_REG(GRClient, get_connection_type);
+ METHOD_REG(GRClient, get_target_send_fps);
+ METHOD_REG(GRClient, get_stretch_mode);
+ METHOD_REG(GRClient, get_texture_filtering);
+ METHOD_REG(GRClient, get_password);
+ METHOD_REG(GRClient, get_device_id);
+ METHOD_REG(GRClient, is_viewport_orientation_syncing);
+ METHOD_REG(GRClient, is_viewport_aspect_ratio_syncing);
+ METHOD_REG(GRClient, is_server_settings_syncing);
+
+ register_property("capture_on_focus", &GRClient::set_capture_on_focus, &GRClient::is_capture_on_focus, false);
+ register_property("capture_when_hover", &GRClient::set_capture_when_hover, &GRClient::is_capture_when_hover, false);
+ register_property("capture_pointer", &GRClient::set_capture_pointer, &GRClient::is_capture_pointer, true);
+ register_property("capture_input", &GRClient::set_capture_input, &GRClient::is_capture_input, true);
+ register_property("connection_type", &GRClient::set_connection_type, &GRClient::get_connection_type, CONNECTION_WiFi, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_ENUM, "WiFi,ADB");
+ register_property("target_send_fps", &GRClient::set_target_send_fps, &GRClient::get_target_send_fps, 60, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_RANGE, "1,1000");
+ register_property("stretch_mode", &GRClient::set_stretch_mode, &GRClient::get_stretch_mode, STRETCH_KEEP_ASPECT, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_ENUM, "Fill,Keep Aspect");
+ register_property("texture_filtering", &GRClient::set_texture_filtering, &GRClient::get_texture_filtering, true);
+ register_property("password", &GRClient::set_password, &GRClient::get_password, "");
+ register_property("device_id", &GRClient::set_device_id, &GRClient::get_device_id, "");
+ register_property("viewport_orientation_syncing", &GRClient::set_viewport_orientation_syncing, &GRClient::is_viewport_orientation_syncing, true);
+ register_property("viewport_aspect_ratio_syncing", &GRClient::set_viewport_aspect_ratio_syncing, &GRClient::is_viewport_aspect_ratio_syncing, true);
+ register_property("server_settings_syncing", &GRClient::set_server_settings_syncing, &GRClient::is_server_settings_syncing, true);
+}
+
+#endif
+
+void GRClient::_notification(int p_notification) {
+ switch (p_notification) {
+ case NOTIFICATION_POSTINITIALIZE:
+#ifndef GDNATIVE_LIBRARY
+ _init();
+#endif
+ break;
+ case NOTIFICATION_PREDELETE: {
+ _deinit();
+ GRDevice::_deinit();
+ break;
+ case NOTIFICATION_EXIT_TREE:
+ is_deleting = true;
+ if (get_status() == (int)WorkingStatus::STATUS_WORKING) {
+ _internal_call_only_deffered_stop();
+ }
+ break;
+ }
+ }
+}
+
+void GRClient::_init() {
+ set_name("GodotRemoteClient");
+ LEAVE_IF_EDITOR();
+
+#ifndef GDNATIVE_LIBRARY
+#else
+ GRDevice::_init();
+#endif
+
+#ifndef GDNATIVE_LIBRARY
+ Math::randomize();
+ device_id = str(Math::randd() * Math::rand()).md5_text().substr(0, 6);
+#else
+ RandomNumberGenerator *rng = memnew(RandomNumberGenerator);
+ rng->randomize();
+ device_id = str(rng->randf() * rng->randf()).md5_text().substr(0, 6);
+ memdelete(rng);
+#endif
+
+ Mutex_create(connection_mutex);
+
+#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
+ no_signal_image.instance();
+ GetPoolVectorFromBin(tmp_no_signal, GRResources::Bin_NoSignalPNG);
+ no_signal_image->load_png_from_buffer(tmp_no_signal);
+
+ no_signal_vertical_image.instance();
+ GetPoolVectorFromBin(tmp_no_signal_vert, GRResources::Bin_NoSignalVerticalPNG);
+ no_signal_vertical_image->load_png_from_buffer(tmp_no_signal_vert);
+
+ Ref shader = newref(Shader);
+ shader->set_code(GRResources::Txt_CRT_Shader);
+ no_signal_mat.instance();
+ no_signal_mat->set_shader(shader);
+#endif
+}
+
+void GRClient::_deinit() {
+ LEAVE_IF_EDITOR();
+
+ is_deleting = true;
+ if (get_status() == (int)WorkingStatus::STATUS_WORKING) {
+ _internal_call_only_deffered_stop();
+ }
+ set_control_to_show_in(nullptr, 0);
+ Mutex_delete(connection_mutex);
+
+#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
+ no_signal_mat.unref();
+ no_signal_image.unref();
+ no_signal_vertical_image.unref();
+#endif
+}
+
+void GRClient::_internal_call_only_deffered_start() {
+ switch ((WorkingStatus)get_status()) {
+ case WorkingStatus::STATUS_WORKING:
+ ERR_FAIL_MSG("Can't start already working GodotRemote Client");
+ case WorkingStatus::STATUS_STARTING:
+ ERR_FAIL_MSG("Can't start already starting GodotRemote Client");
+ case WorkingStatus::STATUS_STOPPING:
+ ERR_FAIL_MSG("Can't start stopping GodotRemote Client");
+ }
+
+ _log("Starting GodotRemote client. Version: " + str(GodotRemote::get_singleton()->get_version()), LogLevel::LL_NORMAL);
+ set_status(WorkingStatus::STATUS_STARTING);
+
+ if (thread_connection) {
+ Mutex_lock(connection_mutex);
+ thread_connection->break_connection = true;
+ thread_connection->stop_thread = true;
+ Mutex_unlock(connection_mutex);
+ thread_connection->close_thread();
+ memdelete(thread_connection);
+ thread_connection = nullptr;
+ }
+ thread_connection = memnew(ConnectionThreadParamsClient);
+ thread_connection->dev = this;
+ thread_connection->peer.instance();
+ Thread_start(thread_connection->thread_ref, GRClient, _thread_connection, thread_connection, this);
+
+ call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
+ set_status(WorkingStatus::STATUS_WORKING);
+}
+
+void GRClient::_internal_call_only_deffered_stop() {
+ switch ((WorkingStatus)get_status()) {
+ case WorkingStatus::STATUS_STOPPED:
+ ERR_FAIL_MSG("Can't stop already stopped GodotRemote Client");
+ case WorkingStatus::STATUS_STOPPING:
+ ERR_FAIL_MSG("Can't stop already stopping GodotRemote Client");
+ case WorkingStatus::STATUS_STARTING:
+ ERR_FAIL_MSG("Can't stop starting GodotRemote Client");
+ }
+
+ _log("Stopping GodotRemote client", LogLevel::LL_DEBUG);
+ set_status(WorkingStatus::STATUS_STOPPING);
+ _remove_custom_input_scene();
+
+ if (thread_connection) {
+ Mutex_lock(connection_mutex);
+ thread_connection->break_connection = true;
+ thread_connection->stop_thread = true;
+ Mutex_unlock(connection_mutex);
+ thread_connection->close_thread();
+ memdelete(thread_connection);
+ thread_connection = nullptr;
+ }
+
+ _send_queue_resize(0);
+
+ call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
+ set_status(WorkingStatus::STATUS_STOPPED);
+}
+
+void GRClient::set_control_to_show_in(Control *ctrl, int position_in_node) {
+ if (tex_shows_stream && !tex_shows_stream->is_queued_for_deletion()) {
+ tex_shows_stream->dev = nullptr;
+ tex_shows_stream->queue_del();
+ tex_shows_stream = nullptr;
+ }
+ if (input_collector && !input_collector->is_queued_for_deletion()) {
+ input_collector->dev = nullptr;
+ input_collector->queue_del();
+ input_collector = nullptr;
+ }
+ if (control_to_show_in && !control_to_show_in->is_queued_for_deletion() &&
+ control_to_show_in->is_connected("resized", this, "_viewport_size_changed")) {
+ control_to_show_in->disconnect("resized", this, "_viewport_size_changed");
+ control_to_show_in->disconnect("tree_exiting", this, "_on_node_deleting");
+ }
+
+ _remove_custom_input_scene();
+
+ control_to_show_in = ctrl;
+
+ if (control_to_show_in && !control_to_show_in->is_queued_for_deletion()) {
+ control_to_show_in->connect("resized", this, "_viewport_size_changed");
+
+ tex_shows_stream = memnew(GRTextureRect);
+ input_collector = memnew(GRInputCollector);
+
+ tex_shows_stream->connect("tree_exiting", this, "_on_node_deleting", vec_args({ (int)DeletingVarName::TEXTURE_TO_SHOW_STREAM }));
+ input_collector->connect("tree_exiting", this, "_on_node_deleting", vec_args({ (int)DeletingVarName::INPUT_COLLECTOR }));
+ control_to_show_in->connect("tree_exiting", this, "_on_node_deleting", vec_args({ (int)DeletingVarName::CONTROL_TO_SHOW_STREAM }));
+
+ tex_shows_stream->set_name("GodotRemoteStreamSprite");
+ input_collector->set_name("GodotRemoteInputCollector");
+
+ tex_shows_stream->set_expand(true);
+ tex_shows_stream->set_anchor(MARGIN_RIGHT, 1.f);
+ tex_shows_stream->set_anchor(MARGIN_BOTTOM, 1.f);
+ tex_shows_stream->dev = this;
+ tex_shows_stream->this_in_client = &tex_shows_stream;
+
+ control_to_show_in->add_child(tex_shows_stream);
+ control_to_show_in->move_child(tex_shows_stream, position_in_node);
+ control_to_show_in->add_child(input_collector);
+
+ input_collector->set_tex_rect(tex_shows_stream);
+ input_collector->dev = this;
+ input_collector->this_in_client = &input_collector;
+
+ signal_connection_state = StreamState::STREAM_ACTIVE; // force execute update function
+ call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
+ call_deferred("_force_update_stream_viewport_signals"); // force update if client connected faster than scene loads
+ }
+}
+
+void GRClient::_on_node_deleting(int var_name) {
+ switch ((DeletingVarName)var_name) {
+ case DeletingVarName::CONTROL_TO_SHOW_STREAM:
+ control_to_show_in = nullptr;
+ set_control_to_show_in(nullptr, 0);
+ break;
+ case DeletingVarName::TEXTURE_TO_SHOW_STREAM:
+ tex_shows_stream = nullptr;
+ break;
+ case DeletingVarName::INPUT_COLLECTOR:
+ input_collector = nullptr;
+ break;
+ case DeletingVarName::CUSTOM_INPUT_SCENE:
+ custom_input_scene = nullptr;
+ break;
+ default:
+ break;
+ }
+}
+
+void GRClient::set_custom_no_signal_texture(Ref custom_tex) {
+ custom_no_signal_texture = custom_tex;
+ call_deferred("_update_stream_texture_state", signal_connection_state);
+}
+
+void GRClient::set_custom_no_signal_vertical_texture(Ref custom_tex) {
+ custom_no_signal_vertical_texture = custom_tex;
+ call_deferred("_update_stream_texture_state", signal_connection_state);
+}
+
+void GRClient::set_custom_no_signal_material(Ref custom_mat) {
+ custom_no_signal_material = custom_mat;
+ call_deferred("_update_stream_texture_state", signal_connection_state);
+}
+
+bool GRClient::is_capture_on_focus() {
+ if (input_collector && !input_collector->is_queued_for_deletion())
+ return input_collector->is_capture_on_focus();
+ return false;
+}
+
+void GRClient::set_capture_on_focus(bool value) {
+ if (input_collector && !input_collector->is_queued_for_deletion())
+ input_collector->set_capture_on_focus(value);
+}
+
+bool GRClient::is_capture_when_hover() {
+ if (input_collector && !input_collector->is_queued_for_deletion())
+ return input_collector->is_capture_when_hover();
+ return false;
+}
+
+void GRClient::set_capture_when_hover(bool value) {
+ if (input_collector && !input_collector->is_queued_for_deletion())
+ input_collector->set_capture_when_hover(value);
+}
+
+bool GRClient::is_capture_pointer() {
+ if (input_collector && !input_collector->is_queued_for_deletion())
+ return input_collector->is_capture_pointer();
+ return false;
+}
+
+void GRClient::set_capture_pointer(bool value) {
+ if (input_collector && !input_collector->is_queued_for_deletion())
+ input_collector->set_capture_pointer(value);
+}
+
+bool GRClient::is_capture_input() {
+ if (input_collector && !input_collector->is_queued_for_deletion())
+ return input_collector->is_capture_input();
+ return false;
+}
+
+void GRClient::set_capture_input(bool value) {
+ if (input_collector && !input_collector->is_queued_for_deletion())
+ input_collector->set_capture_input(value);
+}
+
+void GRClient::set_connection_type(ENUM_ARG(ConnectionType) type) {
+ con_type = (ConnectionType)type;
+}
+
+ENUM_ARG(GRClient::ConnectionType)
+GRClient::get_connection_type() {
+ return con_type;
+}
+
+void GRClient::set_target_send_fps(int fps) {
+ ERR_FAIL_COND(fps <= 0);
+ send_data_fps = fps;
+}
+
+int GRClient::get_target_send_fps() {
+ return send_data_fps;
+}
+
+void GRClient::set_stretch_mode(ENUM_ARG(StretchMode) stretch) {
+ stretch_mode = (StretchMode)stretch;
+ call_deferred("_update_stream_texture_state", signal_connection_state);
+}
+
+ENUM_ARG(GRClient::StretchMode)
+GRClient::get_stretch_mode() {
+ return stretch_mode;
+}
+
+void GRClient::set_texture_filtering(bool is_filtering) {
+ is_filtering_enabled = is_filtering;
+}
+
+bool GRClient::get_texture_filtering() {
+ return is_filtering_enabled;
+}
+
+ENUM_ARG(GRClient::StreamState)
+GRClient::get_stream_state() {
+ return signal_connection_state;
+}
+
+bool GRClient::is_stream_active() {
+ return signal_connection_state;
+}
+
+String GRClient::get_address() {
+ return (String)server_address;
+}
+
+bool GRClient::set_address(String ip) {
+ return set_address_port(ip, port);
+}
+
+bool GRClient::set_address_port(String ip, uint16_t _port) {
+ bool all_ok = false;
+
+#ifndef GDNATIVE_LIBRARY
+ IP_Address adr;
+#else
+ String adr;
+#endif
+
+ if (ip.is_valid_ip_address()) {
+ adr = ip;
+ if (adr.is_valid_ip()) {
+ server_address = ip;
+ port = _port;
+ restart();
+ all_ok = true;
+ } else {
+ _log("Address is invalid: " + ip, LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Resolve Address Error", "Address is invalid: " + ip, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ }
+ } else {
+ adr = IP::get_singleton()->resolve_hostname(adr);
+ if (adr.is_valid_ip()) {
+ _log("Resolved address for " + ip + "\n" + adr, LogLevel::LL_DEBUG);
+ server_address = ip;
+ port = _port;
+ restart();
+ all_ok = true;
+ } else {
+ _log("Can't resolve address for " + ip, LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Resolve Address Error", "Can't resolve address: " + ip, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ }
+ }
+
+ return all_ok;
+}
+
+void GRClient::set_input_buffer(int mb) {
+
+ input_buffer_size_in_mb = mb;
+ restart();
+}
+
+void GRClient::set_viewport_orientation_syncing(bool is_syncing) {
+ _viewport_orientation_syncing = is_syncing;
+ if (is_syncing) {
+ if (input_collector && !input_collector->is_queued_for_deletion()) {
+ _force_update_stream_viewport_signals();
+ }
+ }
+}
+
+bool GRClient::is_viewport_orientation_syncing() {
+ return _viewport_orientation_syncing;
+}
+
+void GRClient::set_viewport_aspect_ratio_syncing(bool is_syncing) {
+ _viewport_aspect_ratio_syncing = is_syncing;
+ if (is_syncing) {
+ call_deferred("_viewport_size_changed"); // force update screen aspect
+ }
+}
+
+bool GRClient::is_viewport_aspect_ratio_syncing() {
+ return _viewport_aspect_ratio_syncing;
+}
+
+void GRClient::set_server_settings_syncing(bool is_syncing) {
+ _server_settings_syncing = is_syncing;
+}
+
+bool GRClient::is_server_settings_syncing() {
+ return _server_settings_syncing;
+}
+
+void GRClient::set_password(String _pass) {
+ password = _pass;
+}
+
+String GRClient::get_password() {
+ return password;
+}
+
+void GRClient::set_device_id(String _id) {
+ ERR_FAIL_COND(_id.empty());
+ device_id = _id;
+}
+
+String GRClient::get_device_id() {
+ return device_id;
+}
+
+bool GRClient::is_connected_to_host() {
+ if (thread_connection && thread_connection->peer.is_valid()) {
+ return thread_connection->peer->is_connected_to_host() && is_connection_working;
+ }
+ return false;
+}
+
+Node *GRClient::get_custom_input_scene() {
+ return custom_input_scene;
+}
+
+void GRClient::_force_update_stream_viewport_signals() {
+ is_vertical = ScreenOrientation::NONE;
+ if (!control_to_show_in || control_to_show_in->is_queued_for_deletion()) {
+ return;
+ }
+
+ call_deferred("_viewport_size_changed"); // force update screen aspect ratio
+}
+
+void GRClient::_load_custom_input_scene(Ref _data) {
+ _remove_custom_input_scene();
+
+ if (_data->get_scene_path().empty() || _data->get_scene_data().size() == 0) {
+ _log("Scene not specified or data is empty. Removing custom input scene", LogLevel::LL_DEBUG);
+ return;
+ }
+
+ if (!control_to_show_in) {
+ _log("Not specified control to show", LogLevel::LL_ERROR);
+ return;
+ }
+
+ Error err = Error::OK;
+#ifndef GDNATIVE_LIBRARY
+ FileAccess *file = FileAccess::open(custom_input_scene_tmp_pck_file, FileAccess::ModeFlags::WRITE, &err);
+#else
+ File *file = memnew(File);
+ err = file->open(custom_input_scene_tmp_pck_file, File::ModeFlags::WRITE);
+#endif
+ if ((int)err) {
+ _log("Can't open temp file to store custom input scene: " + custom_input_scene_tmp_pck_file + ", code: " + str((int)err), LogLevel::LL_ERROR);
+ } else {
+
+ PoolByteArray scene_data;
+ if (_data->is_compressed()) {
+ err = decompress_bytes(_data->get_scene_data(), _data->get_original_size(), scene_data, _data->get_compression_type());
+ } else {
+ scene_data = _data->get_scene_data();
+ }
+
+ if ((int)err) {
+ _log("Can't decompress or set scene_data: Code: " + str((int)err), LogLevel::LL_ERROR);
+ } else {
+
+#ifndef GDNATIVE_LIBRARY
+ auto r = scene_data.read();
+ file->store_buffer(r.ptr(), scene_data.size());
+ release_pva_read(r);
+#else
+ file->store_buffer(scene_data);
+#endif
+ file->close();
+
+#ifndef GDNATIVE_LIBRARY
+ if (PackedData::get_singleton()->is_disabled()) {
+ err = Error::FAILED;
+ } else {
+#if VERSION_MINOR >= 2 && VERSION_PATCH >= 4
+ err = PackedData::get_singleton()->add_pack(custom_input_scene_tmp_pck_file, true, 0);
+#else
+ err = PackedData::get_singleton()->add_pack(custom_input_scene_tmp_pck_file, true);
+#endif
+ }
+#else
+ err = ProjectSettings::get_singleton()->load_resource_pack(custom_input_scene_tmp_pck_file, true, 0) ? Error::OK : Error::FAILED;
+#endif
+
+ if ((int)err) {
+ _log("Can't load PCK file: " + custom_input_scene_tmp_pck_file, LogLevel::LL_ERROR);
+ } else {
+
+#ifndef GDNATIVE_LIBRARY
+ Ref pck = ResourceLoader::load(_data->get_scene_path(), "", false, &err);
+#else
+ Ref pck = ResourceLoader::get_singleton()->load(_data->get_scene_path(), "", false);
+ err = pck->can_instance() ? Error::OK : Error::FAILED;
+#endif
+ if ((int)err) {
+ _log("Can't load scene file: " + _data->get_scene_path() + ", code: " + str((int)err), LogLevel::LL_ERROR);
+ } else {
+
+ custom_input_scene = pck->instance();
+ if (!custom_input_scene) {
+ _log("Can't instance scene from PCK file: " + custom_input_scene_tmp_pck_file + ", scene: " + _data->get_scene_path(), LogLevel::LL_ERROR);
+ } else {
+
+ control_to_show_in->add_child(custom_input_scene);
+ custom_input_scene->connect("tree_exiting", this, "_on_node_deleting", vec_args({ (int)DeletingVarName::CUSTOM_INPUT_SCENE }));
+
+ _reset_counters();
+ emit_signal("custom_input_scene_added");
+ }
+ }
+ }
+ }
+ }
+
+ if (file) {
+ memdelete(file);
+ }
+}
+
+void GRClient::_remove_custom_input_scene() {
+ if (custom_input_scene && !custom_input_scene->is_queued_for_deletion()) {
+
+ custom_input_scene->queue_del();
+ custom_input_scene = nullptr;
+ emit_signal("custom_input_scene_removed");
+
+ Error err = Error::OK;
+#ifndef GDNATIVE_LIBRARY
+ DirAccess *dir = DirAccess::open(custom_input_scene_tmp_pck_file.get_base_dir(), &err);
+#else
+ Directory *dir = memnew(Directory);
+ dir->open(custom_input_scene_tmp_pck_file.get_base_dir());
+#endif
+ if ((int)err) {
+ _log("Can't open folder: " + custom_input_scene_tmp_pck_file.get_base_dir(), LogLevel::LL_ERROR);
+ } else {
+ if (dir && dir->file_exists(custom_input_scene_tmp_pck_file)) {
+ err = dir->remove(custom_input_scene_tmp_pck_file);
+ if ((int)err) {
+ _log("Can't delete file: " + custom_input_scene_tmp_pck_file + ". Code: " + str((int)err), LogLevel::LL_ERROR);
+ }
+ }
+ }
+
+ if (dir) {
+ memdelete(dir);
+ }
+ }
+}
+
+void GRClient::_viewport_size_changed() {
+ if (!control_to_show_in || control_to_show_in->is_queued_for_deletion()) {
+ return;
+ }
+
+ if (_viewport_orientation_syncing) {
+ Vector2 size = control_to_show_in->get_size();
+ ScreenOrientation tmp_vert = size.x < size.y ? ScreenOrientation::VERTICAL : ScreenOrientation::HORIZONTAL;
+ if (tmp_vert != is_vertical) {
+ is_vertical = tmp_vert;
+ Mutex_lock(send_queue_mutex);
+ Ref packet = _find_queued_packet_by_type[ >();
+ if (packet.is_valid()) {
+ packet->set_vertical(is_vertical == ScreenOrientation::VERTICAL);
+ Mutex_unlock(send_queue_mutex);
+ goto ratio_sync;
+ }
+ Mutex_unlock(send_queue_mutex);
+
+ if (packet.is_null()) {
+ packet.instance();
+ packet->set_vertical(is_vertical == ScreenOrientation::VERTICAL);
+ send_packet(packet);
+ }
+ }
+ }
+
+ratio_sync:
+
+ if (_viewport_aspect_ratio_syncing) {
+ Vector2 size = control_to_show_in->get_size();
+
+ Mutex_lock(send_queue_mutex);
+ Ref packet = _find_queued_packet_by_type][ >();
+ if (packet.is_valid()) {
+ packet->set_aspect(size.x / size.y);
+ Mutex_unlock(send_queue_mutex);
+ return;
+ }
+ Mutex_unlock(send_queue_mutex);
+
+ if (packet.is_null()) {
+ packet.instance();
+ packet->set_aspect(size.x / size.y);
+ send_packet(packet);
+ }
+ }
+}
+
+void GRClient::_update_texture_from_image(Ref img) {
+ if (tex_shows_stream && !tex_shows_stream->is_queued_for_deletion()) {
+ if (img.is_valid()) {
+ Ref tex = tex_shows_stream->get_texture();
+ if (tex.is_valid()) {
+ tex->create_from_image(img);
+ } else {
+ tex.instance();
+ tex->create_from_image(img);
+ tex_shows_stream->set_texture(tex);
+ }
+
+ uint32_t new_flags = Texture::FLAG_MIPMAPS | (is_filtering_enabled ? Texture::FLAG_FILTER : 0);
+ if (tex->get_flags() != new_flags) {
+ tex->set_flags(new_flags);
+ }
+ } else {
+ tex_shows_stream->set_texture(nullptr);
+ }
+ }
+}
+
+void GRClient::_update_stream_texture_state(ENUM_ARG(StreamState) _stream_state) {
+ if (is_deleting)
+ return;
+
+ if (tex_shows_stream && !tex_shows_stream->is_queued_for_deletion()) {
+ switch (_stream_state) {
+ case StreamState::STREAM_NO_SIGNAL: {
+ tex_shows_stream->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
+
+ if (custom_no_signal_texture.is_valid() || custom_no_signal_vertical_texture.is_valid()) {
+ tex_shows_stream->set_texture(no_signal_is_vertical ?
+ (custom_no_signal_vertical_texture.is_valid() ? custom_no_signal_vertical_texture : custom_no_signal_texture) :
+ (custom_no_signal_texture.is_valid() ? custom_no_signal_texture : custom_no_signal_vertical_texture));
+ }
+#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
+ else {
+ _update_texture_from_image(no_signal_is_vertical ? no_signal_vertical_image : no_signal_image);
+ }
+#endif
+ if (custom_no_signal_material.is_valid()) {
+ tex_shows_stream->set_material(custom_no_signal_material);
+ }
+#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
+ else {
+ tex_shows_stream->set_material(no_signal_mat);
+ }
+#endif
+ break;
+ }
+ case StreamState::STREAM_ACTIVE: {
+ tex_shows_stream->set_stretch_mode(stretch_mode == StretchMode::STRETCH_KEEP_ASPECT ? TextureRect::STRETCH_KEEP_ASPECT_CENTERED : TextureRect::STRETCH_SCALE);
+ tex_shows_stream->set_material(nullptr);
+ break;
+ }
+ case StreamState::STREAM_NO_IMAGE:
+ tex_shows_stream->set_stretch_mode(TextureRect::STRETCH_SCALE);
+ tex_shows_stream->set_material(nullptr);
+ tex_shows_stream->set_texture(nullptr);
+ break;
+ default:
+ _log("Wrong stream state!", LogLevel::LL_ERROR);
+ break;
+ }
+
+ if (signal_connection_state != _stream_state) {
+ call_deferred("emit_signal", "stream_state_changed", _stream_state);
+ signal_connection_state = (StreamState)_stream_state;
+ }
+ }
+}
+
+void GRClient::_reset_counters() {
+ GRDevice::_reset_counters();
+ sync_time_client = 0;
+ sync_time_server = 0;
+}
+
+void GRClient::set_server_setting(ENUM_ARG(TypesOfServerSettings) param, Variant value) {
+ Mutex_lock(send_queue_mutex);
+ Ref packet = _find_queued_packet_by_type][ >();
+ if (packet.is_valid()) {
+ packet->add_setting(param, value);
+ Mutex_unlock(send_queue_mutex);
+ return;
+ }
+ Mutex_unlock(send_queue_mutex);
+
+ if (packet.is_null()) {
+ packet.instance();
+ packet->add_setting(param, value);
+ send_packet(packet);
+ }
+}
+
+void GRClient::disable_overriding_server_settings() {
+ set_server_setting(TypesOfServerSettings::SERVER_SETTINGS_USE_INTERNAL, true);
+}
+
+//////////////////////////////////////////////
+////////////////// STATIC ////////////////////
+//////////////////////////////////////////////
+
+void GRClient::_thread_connection(THREAD_DATA p_userdata) {
+ ConnectionThreadParamsClient *con_thread = (ConnectionThreadParamsClient *)p_userdata;
+ GRClient *dev = con_thread->dev;
+ Ref con = con_thread->peer;
+
+ OS *os = OS::get_singleton();
+ Thread_set_name("GRemote_connection");
+ GRDevice::AuthResult prev_auth_error = GRDevice::AuthResult::OK;
+
+ const String con_error_title = "Connection Error";
+
+ while (!con_thread->stop_thread) {
+ if (os->get_ticks_usec() - dev->prev_valid_connection_time > 1000_ms) {
+ dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
+ dev->call_deferred("_remove_custom_input_scene");
+ }
+
+ if (con->get_status() == StreamPeerTCP::STATUS_CONNECTED || con->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
+ con->disconnect_from_host();
+ }
+
+ dev->_send_queue_resize(0);
+
+#ifndef GDNATIVE_LIBRARY
+ IP_Address adr;
+#else
+ String adr;
+#endif
+
+ if (dev->con_type == CONNECTION_ADB) {
+#ifndef GDNATIVE_LIBRARY
+ adr = IP_Address("127.0.0.1");
+#else
+ adr = "127.0.0.1";
+#endif
+ } else {
+ if (dev->server_address.is_valid_ip_address()) {
+ adr = dev->server_address;
+ if (adr.is_valid_ip()) {
+ } else {
+ _log("Address is invalid: " + dev->server_address, LogLevel::LL_ERROR);
+ if (prev_auth_error != GRDevice::AuthResult::Error)
+ GRNotifications::add_notification("Resolve Address Error", "Address is invalid: " + dev->server_address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ prev_auth_error = GRDevice::AuthResult::Error;
+ }
+ } else {
+ adr = IP::get_singleton()->resolve_hostname(adr);
+ if (adr.is_valid_ip()) {
+ _log("Resolved address for " + dev->server_address + "\n" + adr, LogLevel::LL_DEBUG);
+ } else {
+ _log("Can't resolve address for " + dev->server_address, LogLevel::LL_ERROR);
+ if (prev_auth_error != GRDevice::AuthResult::Error)
+ GRNotifications::add_notification("Resolve Address Error", "Can't resolve address: " + dev->server_address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ prev_auth_error = GRDevice::AuthResult::Error;
+ }
+ }
+ }
+
+ String address = (String)adr + ":" + str(dev->port);
+ Error err = con->connect_to_host(adr, dev->port);
+
+ _log("Connecting to " + address, LogLevel::LL_DEBUG);
+ if ((int)err) {
+ switch (err) {
+ case Error::FAILED:
+ _log("Failed to open socket or can't connect to host", LogLevel::LL_ERROR);
+ break;
+ case Error::ERR_UNAVAILABLE:
+ _log("Socket is unavailable", LogLevel::LL_ERROR);
+ break;
+ case Error::ERR_INVALID_PARAMETER:
+ _log("Host address is invalid", LogLevel::LL_ERROR);
+ break;
+ case Error::ERR_ALREADY_EXISTS:
+ _log("Socket already in use", LogLevel::LL_ERROR);
+ break;
+ }
+ sleep_usec(250_ms);
+ continue;
+ }
+
+ while (con->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
+ sleep_usec(1_ms);
+ }
+
+ if (con->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
+ _log("Connection timed out with " + address, LogLevel::LL_DEBUG);
+ if (prev_auth_error != GRDevice::AuthResult::Timeout) {
+ GRNotifications::add_notification(con_error_title, "Connection timed out: " + address, GRNotifications::NotificationIcon::ICON_WARNING, true, 1.f);
+ prev_auth_error = GRDevice::AuthResult::Timeout;
+ }
+ sleep_usec(200_ms);
+ continue;
+ }
+
+ con->set_no_delay(true);
+
+ bool long_wait = false;
+
+ Ref ppeer = newref(PacketPeerStream);
+ ppeer->set_stream_peer(con);
+ ppeer->set_input_buffer_max_size(dev->input_buffer_size_in_mb * 1024 * 1024);
+
+ GRDevice::AuthResult res = _auth_on_server(dev, ppeer);
+ switch (res) {
+ case GRDevice::AuthResult::OK: {
+ _log("Successful connected to " + address, LogLevel::LL_NORMAL);
+
+ dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_IMAGE);
+
+ con_thread->break_connection = false;
+ con_thread->peer = con;
+ con_thread->ppeer = ppeer;
+
+ dev->is_connection_working = true;
+ dev->call_deferred("emit_signal", "connection_state_changed", true);
+ dev->call_deferred("_force_update_stream_viewport_signals"); // force update screen aspect ratio and orientation
+ GRNotifications::add_notification("Connected", "Connected to " + address, GRNotifications::NotificationIcon::ICON_SUCCESS, true, 1.f);
+
+ _connection_loop(con_thread);
+
+ con_thread->peer.unref();
+ con_thread->ppeer.unref();
+
+ dev->is_connection_working = false;
+ dev->call_deferred("emit_signal", "connection_state_changed", false);
+ dev->call_deferred("emit_signal", "mouse_mode_changed", Input::MouseMode::MOUSE_MODE_VISIBLE);
+ break;
+ }
+ case GRDevice::AuthResult::Error:
+ if (res != prev_auth_error)
+ GRNotifications::add_notification(con_error_title, "Can't connect to " + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ long_wait = true;
+ break;
+ case GRDevice::AuthResult::Timeout:
+ if (res != prev_auth_error)
+ GRNotifications::add_notification(con_error_title, "Timeout\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ long_wait = true;
+ break;
+ case GRDevice::AuthResult::RefuseConnection:
+ if (res != prev_auth_error)
+ GRNotifications::add_notification(con_error_title, "Connection refused\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ long_wait = true;
+ break;
+ case GRDevice::AuthResult::VersionMismatch:
+ GRNotifications::add_notification(con_error_title, "Version mismatch\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ long_wait = true;
+ break;
+ case GRDevice::AuthResult::IncorrectPassword:
+ GRNotifications::add_notification(con_error_title, "Incorrect password\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ long_wait = true;
+ break;
+ case GRDevice::AuthResult::PasswordRequired:
+ GRNotifications::add_notification(con_error_title, "Required password but it's not implemented.... " + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ break;
+ default:
+ if (res != prev_auth_error)
+ GRNotifications::add_notification(con_error_title, "Unknown error code: " + str((int)res) + "\n" + address, GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ _log("Unknown error code: " + str((int)res) + ". Disconnecting. " + address, LogLevel::LL_NORMAL);
+ break;
+ }
+
+ ((Ref)ppeer->get_stream_peer())->disconnect_from_host();
+ ppeer->set_output_buffer_max_size(0);
+ ppeer->set_input_buffer_max_size(0);
+
+ prev_auth_error = res;
+
+ if (con->is_connected_to_host()) {
+ con->disconnect_from_host();
+ }
+
+ if (long_wait) {
+ sleep_usec(888_ms);
+ }
+ }
+
+ dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_SIGNAL);
+ _log("Connection thread stopped", LogLevel::LL_DEBUG);
+ con_thread->finished = true;
+}
+
+void GRClient::_connection_loop(ConnectionThreadParamsClient *con_thread) {
+ GRClient *dev = con_thread->dev;
+ Ref connection = con_thread->peer;
+ Ref ppeer = con_thread->ppeer;
+ // Data sync with _img_thread
+ ImgProcessingStorageClient *ipsc = memnew(ImgProcessingStorageClient);
+ ipsc->dev = dev;
+
+ Thread_define(_img_thread);
+ Thread_start(_img_thread, GRClient, _thread_image_decoder, ipsc, dev);
+
+ OS *os = OS::get_singleton();
+ Error err = Error::OK;
+ String address = CONNECTION_ADDRESS(connection);
+
+ dev->_reset_counters();
+
+ //Array stream_queue; // Ref
+ std::vector][ > stream_queue; // Ref
+
+ uint64_t time64 = os->get_ticks_usec();
+ uint64_t prev_cycle_time = 0;
+ uint64_t prev_send_input_time = time64;
+ uint64_t prev_ping_sending_time = time64;
+ uint64_t next_image_required_frametime = time64;
+ uint64_t prev_display_image_time = time64 - 16_ms;
+
+ bool ping_sended = false;
+
+ TimeCountInit();
+ while (!con_thread->break_connection && !con_thread->stop_thread && connection->is_connected_to_host()) {
+ Mutex_lock(dev->connection_mutex);
+ TimeCount("Cycle start");
+ uint64_t cycle_start_time = os->get_ticks_usec();
+
+ bool nothing_happens = true;
+ uint64_t start_while_time = 0;
+ dev->prev_valid_connection_time = time64;
+ int send_data_time_us = (1000000 / dev->send_data_fps);
+
+ ///////////////////////////////////////////////////////////////////
+ // SENDING
+ bool is_queued_send = false; // this placed here for android compiler
+
+ // INPUT
+ TimeCountReset();
+ time64 = os->get_ticks_usec();
+ if ((time64 - prev_send_input_time) > send_data_time_us) {
+ prev_send_input_time = time64;
+ nothing_happens = false;
+
+ if (dev->input_collector) {
+ Ref pack = dev->input_collector->get_collected_input_data();
+
+ if (pack.is_valid()) {
+ err = ppeer->put_var(pack->get_data());
+ if ((int)err) {
+ _log("Put input data failed with code: " + str((int)err), LogLevel::LL_ERROR);
+ goto end_send;
+ }
+ } else {
+ _log("Can't get input data from input collector", LogLevel::LL_ERROR);
+ }
+ TimeCount("Input send");
+ }
+ }
+
+ // PING
+ TimeCountReset();
+ time64 = os->get_ticks_usec();
+ if ((time64 - prev_ping_sending_time) > 100_ms && !ping_sended) {
+ nothing_happens = false;
+ ping_sended = true;
+
+ Ref pack(memnew(GRPacketPing));
+ err = ppeer->put_var(pack->get_data());
+ prev_ping_sending_time = time64;
+
+ if ((int)err) {
+ _log("Send ping failed with code: " + str((int)err), LogLevel::LL_ERROR);
+ goto end_send;
+ }
+ TimeCount("Ping send");
+ }
+
+ // SEND QUEUE
+ start_while_time = os->get_ticks_usec();
+ while (!dev->send_queue.empty() && (os->get_ticks_usec() - start_while_time) <= send_data_time_us / 2) {
+ is_queued_send = true;
+ Ref packet = dev->_send_queue_pop_front();
+
+ if (packet.is_valid()) {
+ err = ppeer->put_var(packet->get_data());
+
+ if ((int)err) {
+ _log("Put data from queue failed with code: " + str((int)err), LogLevel::LL_ERROR);
+ goto end_send;
+ }
+ }
+ }
+ if (is_queued_send) {
+ TimeCount("Send queued data");
+ }
+ end_send:
+
+ if (!connection->is_connected_to_host()) {
+ _log("Lost connection after sending!", LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Error", "Lost connection after sending data!", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ Mutex_unlock(dev->connection_mutex);
+ continue;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ // RECEIVING
+
+ // Send to processing one of buffered images
+ time64 = os->get_ticks_usec();
+ TimeCountReset();
+ if (!ipsc->_is_processing_img && !stream_queue.empty() && time64 >= next_image_required_frametime) {
+ nothing_happens = false;
+
+ Ref pack = stream_queue.front();
+ stream_queue.erase(stream_queue.begin());
+
+ if (pack.is_null()) {
+ _log("Queued image data is null", LogLevel::LL_ERROR);
+ goto end_img_process;
+ }
+
+ uint64_t frametime = pack->get_frametime() > 1000_ms ? 1000_ms : pack->get_frametime();
+ next_image_required_frametime = time64 + frametime - prev_cycle_time;
+
+ dev->_update_avg_fps(time64 - prev_display_image_time);
+ prev_display_image_time = time64;
+
+ if (pack->get_is_empty()) {
+ dev->_update_avg_fps(0);
+ dev->call_deferred("_update_texture_from_image", Ref());
+ dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_IMAGE);
+ } else {
+ ipsc->tex_data = pack->get_image_data();
+ ipsc->compression_type = (ImageCompressionType)pack->get_compression_type();
+ ipsc->size = pack->get_size();
+ ipsc->format = pack->get_format();
+ ipsc->_is_processing_img = true;
+ }
+
+ pack.unref();
+ TimeCount("Get image from queue");
+ }
+ end_img_process:
+
+ // check if image displayed less then few seconds ago. if not then remove texture
+ const double image_loss_time = 1.5;
+ if (os->get_ticks_usec() > int64_t(prev_display_image_time + uint64_t(1000_ms * image_loss_time))) {
+ if (dev->signal_connection_state != StreamState::STREAM_NO_IMAGE) {
+ dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_NO_IMAGE);
+ dev->_reset_counters();
+ }
+ }
+
+ if (stream_queue.size() > 10) {
+ //for (int i = 0; i)stream_queue[i]).unref();
+ //}
+ stream_queue.clear();
+ }
+
+ // Get some packets
+ TimeCountReset();
+ start_while_time = os->get_ticks_usec();
+ while (ppeer->get_available_packet_count() > 0 && (os->get_ticks_usec() - start_while_time) <= send_data_time_us / 2) {
+ nothing_happens = false;
+
+#ifndef GDNATIVE_LIBRARY
+ Variant buf;
+ err = ppeer->get_var(buf);
+#else
+ Variant buf = ppeer->get_var();
+#endif
+
+ if ((int)err)
+ goto end_recv;
+
+ Ref pack = GRPacket::create(buf);
+ if (pack.is_null()) {
+ _log("Incorrect GRPacket", LogLevel::LL_ERROR);
+ continue;
+ }
+
+ GRPacket::PacketType type = pack->get_type();
+
+ switch (type) {
+ case GRPacket::PacketType::SyncTime: {
+ Ref data = pack;
+ if (data.is_null()) {
+ _log("Incorrect GRPacketSyncTime", LogLevel::LL_ERROR);
+ continue;
+ }
+
+ dev->sync_time_client = os->get_ticks_usec();
+ dev->sync_time_server = data->get_time();
+
+ break;
+ }
+ case GRPacket::PacketType::ImageData: {
+ Ref data = pack;
+ if (data.is_null()) {
+ _log("Incorrect GRPacketImageData", LogLevel::LL_ERROR);
+ continue;
+ }
+
+ stream_queue.push_back(data);
+ break;
+ }
+ case GRPacket::PacketType::ServerSettings: {
+ if (!dev->_server_settings_syncing) {
+ continue;
+ }
+
+ Ref data = pack;
+ if (data.is_null()) {
+ _log("Incorrect GRPacketServerSettings", LogLevel::LL_ERROR);
+ continue;
+ }
+
+ dev->call_deferred("emit_signal", "server_settings_received", map_to_dict(data->get_settings()));
+ break;
+ }
+ case GRPacket::PacketType::MouseModeSync: {
+ Ref data = pack;
+ if (data.is_null()) {
+ _log("Incorrect GRPacketMouseModeSync", LogLevel::LL_ERROR);
+ continue;
+ }
+
+ dev->call_deferred("emit_signal", "mouse_mode_changed", data->get_mouse_mode());
+ break;
+ }
+ case GRPacket::PacketType::CustomInputScene: {
+ Ref data = pack;
+ if (data.is_null()) {
+ _log("Incorrect GRPacketCustomInputScene", LogLevel::LL_ERROR);
+ continue;
+ }
+
+ dev->call_deferred("_load_custom_input_scene", data);
+ break;
+ }
+ case GRPacket::PacketType::CustomUserData: {
+ Ref data = pack;
+ if (data.is_null()) {
+ _log("Incorrect GRPacketCustomUserData", LogLevel::LL_ERROR);
+ break;
+ }
+ dev->call_deferred("emit_signal", "user_data_received", data->get_packet_id(), data->get_user_data());
+ break;
+ }
+ case GRPacket::PacketType::Ping: {
+ Ref pack(memnew(GRPacketPong));
+ err = ppeer->put_var(pack->get_data());
+ if ((int)err) {
+ _log("Send pong failed with code: " + str((int)err), LogLevel::LL_NORMAL);
+ break;
+ }
+ break;
+ }
+ case GRPacket::PacketType::Pong: {
+ dev->_update_avg_ping(os->get_ticks_usec() - prev_ping_sending_time);
+ ping_sended = false;
+ break;
+ }
+ default:
+ _log("Not supported packet type! " + str((int)type), LogLevel::LL_WARNING);
+ break;
+ }
+ }
+ TimeCount("End receiving");
+ end_recv:
+ Mutex_unlock(dev->connection_mutex);
+
+ if (!connection->is_connected_to_host()) {
+ _log("Lost connection after receiving!", LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Error", "Lost connection after receiving data!", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ continue;
+ }
+
+ if (nothing_happens)
+ sleep_usec(1_ms);
+
+ prev_cycle_time = os->get_ticks_usec() - cycle_start_time;
+ }
+
+ dev->_send_queue_resize(0);
+ stream_queue.clear();
+
+ if (connection->is_connected_to_host()) {
+ _log("Lost connection to " + address, LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Disconnected", "Closing connection to " + address, GRNotifications::NotificationIcon::ICON_FAIL, true, 1.f);
+ } else {
+ _log("Closing connection to " + address, LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Disconnected", "Lost connection to " + address, GRNotifications::NotificationIcon::ICON_FAIL, true, 1.f);
+ }
+
+ ipsc->_thread_closing = true;
+ Thread_close(_img_thread);
+ memdelete(ipsc);
+
+ _log("Closing connection", LogLevel::LL_NORMAL);
+ con_thread->break_connection = true;
+}
+
+void GRClient::_thread_image_decoder(THREAD_DATA p_userdata) {
+ ImgProcessingStorageClient *ipsc = (ImgProcessingStorageClient *)p_userdata;
+ GRClient *dev = ipsc->dev;
+ Error err = Error::OK;
+
+ while (!ipsc->_thread_closing) {
+ if (!ipsc->_is_processing_img) {
+ sleep_usec(1_ms);
+ continue;
+ }
+
+ Ref img(memnew(Image));
+ ImageCompressionType type = ipsc->compression_type;
+
+ TimeCountInit();
+ switch (type) {
+ case ImageCompressionType::COMPRESSION_UNCOMPRESSED: {
+#ifndef GDNATIVE_LIBRARY
+ img->create(ipsc->size.x, ipsc->size.y, false, (Image::Format)ipsc->format, ipsc->tex_data);
+#else
+ img->create_from_data((int)ipsc->size.x, (int)ipsc->size.y, false, (Image::Format)ipsc->format, ipsc->tex_data);
+#endif
+ if (img_is_empty(img)) { // is NOT OK
+ err = Error::FAILED;
+ _log("Incorrect uncompressed image data.", LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Stream Error", "Incorrect uncompressed image data.", GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ }
+ break;
+ }
+ case ImageCompressionType::COMPRESSION_JPG: {
+ err = img->load_jpg_from_buffer(ipsc->tex_data);
+ if ((int)err || img_is_empty(img)) { // is NOT OK
+ _log("Can't decode JPG image.", LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Stream Error", "Can't decode JPG image. Code: " + str((int)err), GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ }
+ break;
+ }
+ case ImageCompressionType::COMPRESSION_PNG: {
+ err = img->load_png_from_buffer(ipsc->tex_data);
+ if ((int)err || img_is_empty(img)) { // is NOT OK
+ _log("Can't decode PNG image.", LogLevel::LL_ERROR);
+ GRNotifications::add_notification("Stream Error", "Can't decode PNG image. Code: " + str((int)err), GRNotifications::NotificationIcon::ICON_ERROR, true, 1.f);
+ }
+ break;
+ }
+ default:
+ _log("Not implemented image decoder type: " + str((int)type), LogLevel::LL_ERROR);
+ break;
+ }
+
+ if (!(int)err) { // is OK
+ TimeCount("Create Image Time");
+ dev->call_deferred("_update_texture_from_image", img);
+
+ if (dev->signal_connection_state != StreamState::STREAM_ACTIVE) {
+ dev->call_deferred("_update_stream_texture_state", StreamState::STREAM_ACTIVE);
+ }
+ }
+
+ ipsc->_is_processing_img = false;
+ }
+}
+
+GRDevice::AuthResult GRClient::_auth_on_server(GRClient *dev, Ref &ppeer) {
+#define wait_packet(_n) \
+ time = (uint32_t)OS::get_singleton()->get_ticks_msec(); \
+ while (ppeer->get_available_packet_count() == 0) { \
+ if (OS::get_singleton()->get_ticks_msec() - time > 150) { \
+ _log("Connection timeout. Disconnecting. Waited: " + str(_n), LogLevel::LL_DEBUG); \
+ goto timeout; \
+ } \
+ if (!con->is_connected_to_host()) { \
+ return GRDevice::AuthResult::Error; \
+ } \
+ sleep_usec(1_ms); \
+ }
+#define packet_error_check(_t) \
+ if ((int)err) { \
+ _log(_t, LogLevel::LL_DEBUG); \
+ return GRDevice::AuthResult::Error; \
+ }
+
+ Ref con = ppeer->get_stream_peer();
+ String address = CONNECTION_ADDRESS(con);
+ uint32_t time = 0;
+
+ Error err = Error::OK;
+ Variant ret;
+ // GET first packet
+ wait_packet("first_packet");
+#ifndef GDNATIVE_LIBRARY
+ err = ppeer->get_var(ret);
+ packet_error_check("Can't get first authorization packet from server. Code: " + str((int)err));
+#else
+ err = Error::OK;
+ ret = ppeer->get_var();
+#endif
+
+ if ((int)ret == (int)GRDevice::AuthResult::RefuseConnection) {
+ _log("Connection refused", LogLevel::LL_ERROR);
+ return GRDevice::AuthResult::RefuseConnection;
+ }
+ if ((int)ret == (int)GRDevice::AuthResult::TryToConnect) {
+ Dictionary data;
+ data["id"] = dev->device_id;
+ data["version"] = get_gr_version();
+ data["password"] = dev->password;
+
+ // PUT auth data
+ err = ppeer->put_var(data);
+ packet_error_check("Can't put authorization data to server. Code: " + str((int)err));
+
+ // GET result
+ wait_packet("result");
+#ifndef GDNATIVE_LIBRARY
+ err = ppeer->get_var(ret);
+ packet_error_check("Can't get final authorization packet from server. Code: " + str((int)err));
+#else
+ err = Error::OK;
+ ret = ppeer->get_var();
+#endif
+
+ if ((int)ret == (int)GRDevice::AuthResult::OK) {
+ return GRDevice::AuthResult::OK;
+ } else {
+ GRDevice::AuthResult r = (GRDevice::AuthResult)(int)ret;
+ switch (r) {
+ case GRDevice::AuthResult::Error:
+ _log("Can't connect to server", LogLevel::LL_ERROR);
+ return r;
+ case GRDevice::AuthResult::VersionMismatch:
+ _log("Version mismatch", LogLevel::LL_ERROR);
+ return r;
+ case GRDevice::AuthResult::IncorrectPassword:
+ _log("Incorrect password", LogLevel::LL_ERROR);
+ return r;
+ }
+ }
+ }
+
+ return GRDevice::AuthResult::Error;
+
+timeout:
+ con->disconnect_from_host();
+ _log("Connection timeout. Disconnecting", LogLevel::LL_NORMAL);
+ return GRDevice::AuthResult::Timeout;
+
+#undef wait_packet
+#undef packet_error_check
+}
+
+//////////////////////////////////////////////
+///////////// INPUT COLLECTOR ////////////////
+//////////////////////////////////////////////
+
+void GRInputCollector::_update_stream_rect() {
+ if (!dev || dev->get_status() != GRDevice::WorkingStatus::STATUS_WORKING)
+ return;
+
+ if (texture_rect && !texture_rect->is_queued_for_deletion()) {
+ switch (dev->get_stretch_mode()) {
+ case GRClient::StretchMode::STRETCH_KEEP_ASPECT: {
+ Ref tex = texture_rect->get_texture();
+ if (tex.is_null())
+ goto fill;
+
+ Vector2 pos = texture_rect->get_global_position();
+ Vector2 outer_size = texture_rect->get_size();
+ Vector2 inner_size = tex->get_size();
+ float asp_rec = outer_size.x / outer_size.y;
+ float asp_tex = inner_size.x / inner_size.y;
+
+ if (asp_rec > asp_tex) {
+ float width = outer_size.y * asp_tex;
+ stream_rect = Rect2(Vector2(pos.x + (outer_size.x - width) / 2, pos.y), Vector2(width, outer_size.y));
+ return;
+ } else {
+ float height = outer_size.x / asp_tex;
+ stream_rect = Rect2(Vector2(pos.x, pos.y + (outer_size.y - height) / 2), Vector2(outer_size.x, height));
+ return;
+ }
+ break;
+ }
+ case GRClient::StretchMode::STRETCH_FILL:
+ default:
+ fill:
+ stream_rect = Rect2(texture_rect->get_global_position(), texture_rect->get_size());
+ return;
+ }
+ }
+ if (parent && !parent->is_queued_for_deletion()) {
+ stream_rect = Rect2(parent->get_global_position(), parent->get_size());
+ }
+ return;
+}
+
+void GRInputCollector::_collect_input(Ref ie) {
+ Ref data = GRInputDataEvent::parse_event(ie, stream_rect);
+ if (data.is_valid()) {
+ _TS_LOCK_;
+ collected_input_data.push_back(data);
+ _TS_UNLOCK_;
+ }
+}
+
+void GRInputCollector::_release_pointers() {
+ {
+ auto buttons = mouse_buttons.keys();
+ for (int i = 0; i < buttons.size(); i++) {
+ if (mouse_buttons[buttons[i]]) {
+ Ref iemb(memnew(InputEventMouseButton));
+ iemb->set_button_index(buttons[i]);
+ iemb->set_pressed(false);
+ buttons[i] = false;
+ _collect_input(iemb);
+ }
+ }
+ buttons.clear();
+ }
+
+ {
+ auto touches = screen_touches.keys();
+ for (int i = 0; i < touches.size(); i++) {
+ if (screen_touches[touches[i]]) {
+ Ref iest(memnew(InputEventScreenTouch));
+ iest->set_index(touches[i]);
+ iest->set_pressed(false);
+ touches[i] = false;
+ _collect_input(iest);
+ }
+ }
+ touches.clear();
+ }
+}
+
+#ifndef GDNATIVE_LIBRARY
+
+void GRInputCollector::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_input", "input_event"), &GRInputCollector::_input);
+
+ ClassDB::bind_method(D_METHOD("is_capture_on_focus"), &GRInputCollector::is_capture_on_focus);
+ ClassDB::bind_method(D_METHOD("set_capture_on_focus", "value"), &GRInputCollector::set_capture_on_focus);
+ ClassDB::bind_method(D_METHOD("is_capture_when_hover"), &GRInputCollector::is_capture_when_hover);
+ ClassDB::bind_method(D_METHOD("set_capture_when_hover", "value"), &GRInputCollector::set_capture_when_hover);
+ ClassDB::bind_method(D_METHOD("is_capture_pointer"), &GRInputCollector::is_capture_pointer);
+ ClassDB::bind_method(D_METHOD("set_capture_pointer", "value"), &GRInputCollector::set_capture_pointer);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_on_focus"), "set_capture_on_focus", "is_capture_on_focus");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_when_hover"), "set_capture_when_hover", "is_capture_when_hover");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_pointer"), "set_capture_pointer", "is_capture_pointer");
+}
+
+#else
+
+void GRInputCollector::_register_methods() {
+ METHOD_REG(GRInputCollector, _notification);
+
+ METHOD_REG(GRInputCollector, _input);
+
+ METHOD_REG(GRInputCollector, is_capture_on_focus);
+ METHOD_REG(GRInputCollector, set_capture_on_focus);
+ METHOD_REG(GRInputCollector, is_capture_when_hover);
+ METHOD_REG(GRInputCollector, set_capture_when_hover);
+ METHOD_REG(GRInputCollector, is_capture_pointer);
+ METHOD_REG(GRInputCollector, set_capture_pointer);
+
+ register_property("capture_on_focus", &GRInputCollector::set_capture_on_focus, &GRInputCollector::is_capture_on_focus, false);
+ register_property("capture_when_hover", &GRInputCollector::set_capture_when_hover, &GRInputCollector::is_capture_when_hover, true);
+ register_property("capture_pointer", &GRInputCollector::set_capture_pointer, &GRInputCollector::is_capture_pointer, true);
+}
+
+#endif
+
+void GRInputCollector::_input(Ref ie) {
+ if (!parent || (capture_only_when_control_in_focus && !parent->has_focus()) ||
+ (dev && dev->get_status() != GRDevice::WorkingStatus::STATUS_WORKING) ||
+ !dev->is_stream_active() || !is_inside_tree()) {
+ return;
+ }
+
+ _TS_LOCK_;
+ if (collected_input_data.size() >= 256) {
+ collected_input_data.resize(0);
+ }
+ _TS_UNLOCK_;
+
+ _update_stream_rect();
+
+ if (ie.is_null()) {
+ _log("InputEvent is null", LogLevel::LL_ERROR);
+ return;
+ }
+
+ {
+ Ref iemb = ie;
+ if (iemb.is_valid()) {
+ int idx = (int)iemb->get_button_index();
+
+ if ((!stream_rect.has_point(iemb->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer) {
+ if (idx == BUTTON_WHEEL_UP || idx == BUTTON_WHEEL_DOWN ||
+ idx == BUTTON_WHEEL_LEFT || idx == BUTTON_WHEEL_RIGHT) {
+ return;
+ } else {
+ if (iemb->is_pressed() || !((bool)mouse_buttons[idx]))
+ return;
+ }
+ }
+
+ mouse_buttons[idx] = iemb->is_pressed();
+ goto end;
+ }
+ }
+
+ {
+ Ref iemm = ie;
+ if (iemm.is_valid()) {
+ if ((!stream_rect.has_point(iemm->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer)
+ return;
+ goto end;
+ }
+ }
+
+ {
+ Ref iest = ie;
+ if (iest.is_valid()) {
+ int idx = (int)iest->get_index();
+ if ((!stream_rect.has_point(iest->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer) {
+ if (iest->is_pressed() || !((bool)screen_touches[idx]))
+ return;
+ }
+
+ screen_touches[idx] = iest->is_pressed();
+ goto end;
+ }
+ }
+
+ {
+ Ref iesd = ie;
+ if (iesd.is_valid()) {
+ if ((!stream_rect.has_point(iesd->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer)
+ return;
+ goto end;
+ }
+ }
+
+ {
+ Ref iemg = ie;
+ if (iemg.is_valid()) {
+ if ((!stream_rect.has_point(iemg->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer)
+ return;
+ goto end;
+ }
+ }
+
+ {
+ Ref iepg = ie;
+ if (iepg.is_valid()) {
+ if ((!stream_rect.has_point(iepg->get_position()) && capture_pointer_only_when_hover_control) || dont_capture_pointer)
+ return;
+ goto end;
+ }
+ }
+
+end:
+
+ _collect_input(ie);
+}
+
+void GRInputCollector::_notification(int p_notification) {
+ switch (p_notification) {
+ case NOTIFICATION_POSTINITIALIZE:
+#ifndef GDNATIVE_LIBRARY
+ _init();
+#endif
+ break;
+ case NOTIFICATION_PREDELETE:
+ _deinit();
+ break;
+ case NOTIFICATION_ENTER_TREE: {
+ parent = cast_to(get_parent());
+ break;
+ }
+ case NOTIFICATION_EXIT_TREE: {
+ parent = nullptr;
+ break;
+ }
+ case NOTIFICATION_PROCESS: {
+ _TS_LOCK_;
+ auto w = sensors.write();
+ w[0] = Input::get_singleton()->get_accelerometer();
+ w[1] = Input::get_singleton()->get_gravity();
+ w[2] = Input::get_singleton()->get_gyroscope();
+ w[3] = Input::get_singleton()->get_magnetometer();
+ release_pva_write(w);
+ _TS_UNLOCK_;
+ break;
+ }
+ }
+}
+
+bool GRInputCollector::is_capture_on_focus() {
+ return capture_only_when_control_in_focus;
+}
+
+void GRInputCollector::set_capture_on_focus(bool value) {
+ capture_only_when_control_in_focus = value;
+}
+
+bool GRInputCollector::is_capture_when_hover() {
+ return capture_pointer_only_when_hover_control;
+}
+
+void GRInputCollector::set_capture_when_hover(bool value) {
+ capture_pointer_only_when_hover_control = value;
+}
+
+bool GRInputCollector::is_capture_pointer() {
+ return !dont_capture_pointer;
+}
+
+void GRInputCollector::set_capture_pointer(bool value) {
+ if (!value) {
+ _release_pointers();
+ }
+ dont_capture_pointer = !value;
+}
+
+bool GRInputCollector::is_capture_input() {
+ return is_processing_input();
+}
+
+void GRInputCollector::set_capture_input(bool value) {
+ set_process_input(value);
+}
+
+void GRInputCollector::set_tex_rect(TextureRect *tr) {
+ texture_rect = tr;
+}
+
+Ref GRInputCollector::get_collected_input_data() {
+ Ref res(memnew(GRPacketInputData));
+ Ref s(memnew(GRInputDeviceSensorsData));
+
+ _TS_LOCK_;
+
+ s->set_sensors(sensors);
+ collected_input_data.push_back(s);
+ res->set_input_data(collected_input_data);
+ collected_input_data.resize(0);
+
+ _TS_UNLOCK_;
+ return res;
+}
+
+void GRInputCollector::_init() {
+ LEAVE_IF_EDITOR();
+ _TS_LOCK_;
+ parent = nullptr;
+ set_process(true);
+ set_process_input(true);
+ sensors.resize(4);
+ _TS_UNLOCK_;
+}
+
+void GRInputCollector::_deinit() {
+ LEAVE_IF_EDITOR();
+ _TS_LOCK_;
+ sensors.resize(0);
+ collected_input_data.resize(0);
+ if (this_in_client)
+ *this_in_client = nullptr;
+ mouse_buttons.clear();
+ screen_touches.clear();
+ _TS_UNLOCK_;
+}
+
+//////////////////////////////////////////////
+/////////////// TEXTURE RECT /////////////////
+//////////////////////////////////////////////
+
+void GRTextureRect::_tex_size_changed() {
+ if (dev) {
+ Vector2 v = get_size();
+ bool is_vertical = v.x < v.y;
+ if (is_vertical != dev->no_signal_is_vertical) {
+ dev->no_signal_is_vertical = is_vertical;
+ dev->_update_stream_texture_state(dev->signal_connection_state); // update texture
+ }
+ }
+}
+
+#ifndef GDNATIVE_LIBRARY
+
+void GRTextureRect::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_tex_size_changed"), &GRTextureRect::_tex_size_changed);
+}
+
+#else
+
+void GRTextureRect::_register_methods() {
+ METHOD_REG(GRTextureRect, _notification);
+
+ METHOD_REG(GRTextureRect, _tex_size_changed);
+}
+
+#endif
+
+void GRTextureRect::_notification(int p_notification) {
+ switch (p_notification) {
+ case NOTIFICATION_POSTINITIALIZE:
+#ifndef GDNATIVE_LIBRARY
+ _init();
+#endif
+ break;
+ case NOTIFICATION_PREDELETE:
+ _deinit();
+ break;
+ }
+}
+
+void GRTextureRect::_init() {
+ LEAVE_IF_EDITOR();
+ connect("resized", this, "_tex_size_changed");
+}
+
+void GRTextureRect::_deinit() {
+ if (this_in_client)
+ *this_in_client = nullptr;
+ LEAVE_IF_EDITOR();
+ disconnect("resized", this, "_tex_size_changed");
+}
+
+#endif // !NO_GODOTREMOTE_CLIENT
diff --git a/modules/godot_remote/godot_remote/GRClient.h b/modules/godot_remote/godot_remote/GRClient.h
new file mode 100644
index 0000000..2776e46
--- /dev/null
+++ b/modules/godot_remote/godot_remote/GRClient.h
@@ -0,0 +1,338 @@
+/* GRClient.h */
+#pragma once
+
+#ifndef NO_GODOTREMOTE_CLIENT
+
+#include "GRDevice.h"
+
+#ifndef GDNATIVE_LIBRARY
+#include "core/io/ip_address.h"
+#include "core/io/stream_peer_tcp.h"
+#include "scene/gui/texture_rect.h"
+#include "scene/main/node.h"
+
+#else
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+using namespace godot;
+#endif
+
+class GRClient : public GRDevice {
+ GD_S_CLASS(GRClient, GRDevice);
+
+ friend class GRTextureRect;
+
+ enum class ScreenOrientation : int {
+ NONE = 0,
+ VERTICAL = 1,
+ HORIZONTAL = 2,
+ };
+
+public:
+ enum ConnectionType : int {
+ CONNECTION_WiFi = 0,
+ CONNECTION_ADB = 1,
+ };
+
+ enum StretchMode : int {
+ STRETCH_KEEP_ASPECT = 0,
+ STRETCH_FILL = 1,
+ };
+
+ enum StreamState : int {
+ STREAM_NO_SIGNAL = 0,
+ STREAM_ACTIVE = 1,
+ STREAM_NO_IMAGE = 2,
+ };
+
+#ifndef GDNATIVE_LIBRARY
+private:
+#else
+public:
+#endif
+
+ class ImgProcessingStorageClient : public Object {
+ GD_CLASS(ImgProcessingStorageClient, Object);
+
+ public:
+ GRClient *dev = nullptr;
+ PoolByteArray tex_data;
+ uint64_t framerate = 0;
+ int format = 0;
+ ImageCompressionType compression_type = ImageCompressionType::COMPRESSION_UNCOMPRESSED;
+ Size2 size;
+ bool _is_processing_img = false;
+ bool _thread_closing = false;
+
+ static void _register_methods(){};
+ void _init() {
+ LEAVE_IF_EDITOR();
+ tex_data = PoolByteArray();
+ };
+
+ ~ImgProcessingStorageClient() {
+ LEAVE_IF_EDITOR();
+ tex_data.resize(0);
+ }
+ };
+
+ class ConnectionThreadParamsClient : public Object {
+ GD_CLASS(ConnectionThreadParamsClient, Object);
+
+ public:
+ GRClient *dev = nullptr;
+ Ref peer;
+ Ref ppeer;
+
+ Thread_define(thread_ref);
+
+ bool break_connection = false;
+ bool stop_thread = false;
+ bool finished = false;
+
+ void close_thread() {
+ break_connection = true;
+ stop_thread = true;
+ Thread_close(thread_ref);
+ }
+
+ static void _register_methods(){};
+ void _init(){};
+
+ ~ConnectionThreadParamsClient() {
+ LEAVE_IF_EDITOR();
+ close_thread();
+ if (peer.is_valid()) {
+ peer.unref();
+ }
+ if (ppeer.is_valid()) {
+ ppeer.unref();
+ }
+ };
+ };
+
+private:
+ bool is_deleting = false;
+ bool is_connection_working = false;
+ Node *settings_menu_node = nullptr;
+ class Control *control_to_show_in = nullptr;
+ class GRTextureRect *tex_shows_stream = nullptr;
+ class GRInputCollector *input_collector = nullptr;
+ ConnectionThreadParamsClient *thread_connection = nullptr;
+ ScreenOrientation is_vertical = ScreenOrientation::NONE;
+
+ String device_id = "UNKNOWN";
+ String server_address = String("127.0.0.1");
+
+ String password;
+ bool is_filtering_enabled = true;
+ bool _viewport_orientation_syncing = true;
+ bool _viewport_aspect_ratio_syncing = true;
+ bool _server_settings_syncing = false;
+ StretchMode stretch_mode = StretchMode::STRETCH_KEEP_ASPECT;
+
+ Mutex_define(connection_mutex);
+ ConnectionType con_type = ConnectionType::CONNECTION_WiFi;
+ int input_buffer_size_in_mb = 4;
+ int send_data_fps = 60;
+
+ uint64_t sync_time_client = 0;
+ uint64_t sync_time_server = 0;
+
+ // NO SIGNAL screen
+ uint64_t prev_valid_connection_time = 0;
+ StreamState signal_connection_state = StreamState::STREAM_NO_SIGNAL;
+ bool no_signal_is_vertical = false;
+ Ref custom_no_signal_texture;
+ Ref custom_no_signal_vertical_texture;
+ Ref custom_no_signal_material;
+
+#ifndef NO_GODOTREMOTE_DEFAULT_RESOURCES
+ Ref no_signal_image;
+ Ref no_signal_vertical_image;
+ Ref no_signal_mat;
+#endif
+
+ Node *custom_input_scene = nullptr;
+ String custom_input_scene_tmp_pck_file = "user://custom_input_scene.pck";
+
+ void _force_update_stream_viewport_signals();
+ void _load_custom_input_scene(Ref _data);
+ void _remove_custom_input_scene();
+ void _viewport_size_changed();
+ void _on_node_deleting(int var_name);
+
+ void _update_texture_from_image(Ref img);
+ void _update_stream_texture_state(ENUM_ARG(StreamState) _stream_state);
+ virtual void _reset_counters() override;
+
+ THREAD_FUNC void _thread_connection(THREAD_DATA p_userdata);
+ THREAD_FUNC void _thread_image_decoder(THREAD_DATA p_userdata);
+
+ static void _connection_loop(ConnectionThreadParamsClient *con_thread);
+ static GRDevice::AuthResult _auth_on_server(GRClient *dev, Ref &con);
+
+protected:
+ virtual void _internal_call_only_deffered_start() override;
+ virtual void _internal_call_only_deffered_stop() override;
+
+#ifndef GDNATIVE_LIBRARY
+ static void _bind_methods();
+#else
+public:
+ static void _register_methods();
+
+protected:
+#endif
+
+ void _notification(int p_notification);
+
+public:
+ void set_control_to_show_in(class Control *ctrl, int position_in_node DEF_ARG(= 0));
+ void set_custom_no_signal_texture(Ref custom_tex);
+ void set_custom_no_signal_vertical_texture(Ref custom_tex);
+ void set_custom_no_signal_material(Ref custom_mat);
+
+ bool is_capture_on_focus();
+ void set_capture_on_focus(bool value);
+ bool is_capture_when_hover();
+ void set_capture_when_hover(bool value);
+ bool is_capture_pointer();
+ void set_capture_pointer(bool value);
+ bool is_capture_input();
+ void set_capture_input(bool value);
+ void set_connection_type(ENUM_ARG(ConnectionType) type);
+ ENUM_ARG(ConnectionType)
+ get_connection_type();
+ void set_target_send_fps(int fps);
+ int get_target_send_fps();
+ void set_stretch_mode(ENUM_ARG(StretchMode) stretch);
+ ENUM_ARG(StretchMode)
+ get_stretch_mode();
+ void set_texture_filtering(bool is_filtering);
+ bool get_texture_filtering();
+ void set_viewport_orientation_syncing(bool is_syncing);
+ bool is_viewport_orientation_syncing();
+ void set_viewport_aspect_ratio_syncing(bool is_syncing);
+ bool is_viewport_aspect_ratio_syncing();
+ void set_server_settings_syncing(bool is_syncing);
+ bool is_server_settings_syncing();
+ void set_password(String _pass);
+ String get_password();
+ void set_device_id(String _id);
+ String get_device_id();
+
+ ENUM_ARG(StreamState)
+ get_stream_state();
+ bool is_stream_active();
+ bool is_connected_to_host();
+ Node *get_custom_input_scene();
+ String get_address();
+ bool set_address(String ip);
+ bool set_address_port(String ip, uint16_t _port);
+ void set_input_buffer(int mb);
+
+ void set_server_setting(ENUM_ARG(TypesOfServerSettings) param, Variant value);
+ void disable_overriding_server_settings();
+
+ void _init();
+ void _deinit();
+};
+
+class GRInputCollector : public Node {
+ GD_CLASS(GRInputCollector, Node);
+ friend GRClient;
+
+ _TS_CLASS_;
+
+private:
+ GRClient *dev = nullptr;
+ GRInputCollector **this_in_client = nullptr; //somebody help
+
+ class TextureRect *texture_rect = nullptr;
+ //Array collected_input_data; // Ref
+ std::vector][ > collected_input_data;
+ class Control *parent;
+ bool capture_only_when_control_in_focus = false;
+ bool capture_pointer_only_when_hover_control = true;
+ bool dont_capture_pointer = false;
+
+ Rect2 stream_rect;
+ PoolVector3Array sensors;
+
+ Dictionary mouse_buttons;
+ Dictionary screen_touches;
+
+protected:
+ void _collect_input(Ref ie);
+ void _update_stream_rect();
+ void _release_pointers();
+
+#ifndef GDNATIVE_LIBRARY
+ static void _bind_methods();
+#else
+public:
+ static void _register_methods();
+
+protected:
+#endif
+
+ void _input(Ref ie);
+ void _notification(int p_notification);
+
+public:
+ bool is_capture_on_focus();
+ void set_capture_on_focus(bool value);
+ bool is_capture_when_hover();
+ void set_capture_when_hover(bool value);
+ bool is_capture_pointer();
+ void set_capture_pointer(bool value);
+ bool is_capture_input();
+ void set_capture_input(bool value);
+
+ void set_tex_rect(class TextureRect *tr);
+
+ Ref get_collected_input_data();
+
+ void _init();
+ void _deinit();
+};
+
+class GRTextureRect : public TextureRect {
+ GD_CLASS(GRTextureRect, TextureRect);
+ friend GRClient;
+
+ GRClient *dev = nullptr;
+ GRTextureRect **this_in_client = nullptr;
+ void _tex_size_changed();
+
+protected:
+#ifndef GDNATIVE_LIBRARY
+ static void _bind_methods();
+#else
+public:
+ static void _register_methods();
+
+protected:
+#endif
+
+ void _notification(int p_notification);
+
+public:
+ void _init();
+ void _deinit();
+};
+
+#ifndef GDNATIVE_LIBRARY
+VARIANT_ENUM_CAST(GRClient::ConnectionType)
+VARIANT_ENUM_CAST(GRClient::StretchMode)
+VARIANT_ENUM_CAST(GRClient::StreamState)
+#endif
+
+#endif // !NO_GODOTREMOTE_CLIENT
diff --git a/modules/godot_remote/godot_remote/GRDevice.cpp b/modules/godot_remote/godot_remote/GRDevice.cpp
new file mode 100644
index 0000000..e99ae61
--- /dev/null
+++ b/modules/godot_remote/godot_remote/GRDevice.cpp
@@ -0,0 +1,255 @@
+/* GRDevice.cpp */
+#include "GRDevice.h"
+#include "GodotRemote.h"
+
+#ifndef GDNATIVE_LIBRARY
+#else
+#include
+using namespace godot;
+#endif
+
+using namespace GRUtils;
+
+#ifndef GDNATIVE_LIBRARY
+
+void GRDevice::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_internal_call_only_deffered_start"), &GRDevice::_internal_call_only_deffered_start);
+ ClassDB::bind_method(D_METHOD("_internal_call_only_deffered_stop"), &GRDevice::_internal_call_only_deffered_stop);
+
+ ClassDB::bind_method(D_METHOD("_internal_call_only_deffered_restart"), &GRDevice::_internal_call_only_deffered_restart);
+
+ ClassDB::bind_method(D_METHOD("get_avg_ping"), &GRDevice::get_avg_ping);
+ ClassDB::bind_method(D_METHOD("get_min_ping"), &GRDevice::get_min_ping);
+ ClassDB::bind_method(D_METHOD("get_max_ping"), &GRDevice::get_max_ping);
+ ClassDB::bind_method(D_METHOD("get_avg_fps"), &GRDevice::get_avg_fps);
+ ClassDB::bind_method(D_METHOD("get_min_fps"), &GRDevice::get_min_fps);
+ ClassDB::bind_method(D_METHOD("get_max_fps"), &GRDevice::get_max_fps);
+
+ ClassDB::bind_method(D_METHOD("get_port"), &GRDevice::get_port);
+ ClassDB::bind_method(D_METHOD("set_port", "port"), &GRDevice::set_port, DEFVAL(52341));
+
+ //ClassDB::bind_method(D_METHOD("send_packet", "packet"), &GRDevice::send_packet);
+ ClassDB::bind_method(D_METHOD("send_user_data", "packet_id", "user_data", "full_objects"), &GRDevice::send_user_data, DEFVAL(false));
+
+ ClassDB::bind_method(D_METHOD("start"), &GRDevice::start);
+ ClassDB::bind_method(D_METHOD("stop"), &GRDevice::stop);
+ ClassDB::bind_method(D_METHOD("get_status"), &GRDevice::get_status);
+
+ ADD_SIGNAL(MethodInfo("status_changed", PropertyInfo(Variant::INT, "status")));
+ ADD_SIGNAL(MethodInfo("user_data_received", PropertyInfo(Variant::NIL, "packet_id"), PropertyInfo(Variant::NIL, "user_data")));
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "port"), "set_port", "get_port");
+
+ BIND_ENUM_CONSTANT(STATUS_STARTING);
+ BIND_ENUM_CONSTANT(STATUS_STOPPING);
+ BIND_ENUM_CONSTANT(STATUS_WORKING);
+ BIND_ENUM_CONSTANT(STATUS_STOPPED);
+
+ BIND_ENUM_CONSTANT(SERVER_SETTINGS_USE_INTERNAL);
+ BIND_ENUM_CONSTANT(SERVER_SETTINGS_VIDEO_STREAM_ENABLED);
+ BIND_ENUM_CONSTANT(SERVER_SETTINGS_COMPRESSION_TYPE);
+ BIND_ENUM_CONSTANT(SERVER_SETTINGS_JPG_QUALITY);
+ BIND_ENUM_CONSTANT(SERVER_SETTINGS_SKIP_FRAMES);
+ BIND_ENUM_CONSTANT(SERVER_SETTINGS_RENDER_SCALE);
+
+ BIND_ENUM_CONSTANT(SUBSAMPLING_Y_ONLY);
+ BIND_ENUM_CONSTANT(SUBSAMPLING_H1V1);
+ BIND_ENUM_CONSTANT(SUBSAMPLING_H2V1);
+ BIND_ENUM_CONSTANT(SUBSAMPLING_H2V2);
+
+ BIND_ENUM_CONSTANT(COMPRESSION_UNCOMPRESSED);
+ BIND_ENUM_CONSTANT(COMPRESSION_JPG);
+ BIND_ENUM_CONSTANT(COMPRESSION_PNG);
+}
+
+#else
+
+void GRDevice::_register_methods() {
+ METHOD_REG(GRDevice, _notification);
+
+ METHOD_REG(GRDevice, _internal_call_only_deffered_start);
+ METHOD_REG(GRDevice, _internal_call_only_deffered_stop);
+
+ METHOD_REG(GRDevice, _internal_call_only_deffered_restart);
+
+ METHOD_REG(GRDevice, get_avg_ping);
+ METHOD_REG(GRDevice, get_min_ping);
+ METHOD_REG(GRDevice, get_max_ping);
+ METHOD_REG(GRDevice, get_avg_fps);
+ METHOD_REG(GRDevice, get_min_fps);
+ METHOD_REG(GRDevice, get_max_fps);
+
+ METHOD_REG(GRDevice, get_port);
+ METHOD_REG(GRDevice, set_port);
+
+ //METHOD_REG(GRDevice, send_packet);
+ METHOD_REG(GRDevice, send_user_data);
+
+ METHOD_REG(GRDevice, start);
+ METHOD_REG(GRDevice, stop);
+ METHOD_REG(GRDevice, get_status);
+
+ register_signal("status_changed", "status", GODOT_VARIANT_TYPE_INT);
+ register_signal("user_data_received", "packet_id", GODOT_VARIANT_TYPE_NIL, "user_data", GODOT_VARIANT_TYPE_NIL);
+
+ register_property("port", &GRDevice::set_port, &GRDevice::get_port, 52341);
+}
+
+#endif
+
+void GRDevice::_notification(int p_notification) {
+ switch (p_notification) {
+ case NOTIFICATION_POSTINITIALIZE:
+#ifndef GDNATIVE_LIBRARY
+ _init();
+#endif
+ break;
+ case NOTIFICATION_PREDELETE:
+ _deinit();
+ break;
+ }
+}
+
+void GRDevice::_reset_counters() {
+ avg_fps = min_fps = max_fps = 0;
+ avg_ping = min_ping = max_ping = 0;
+ fps_queue = ping_queue = iterable_queue();
+}
+
+void GRDevice::_update_avg_ping(uint64_t ping) {
+ ping_queue.add_value_limited(ping, avg_ping_max_count);
+ calculate_avg_min_max_values(ping_queue, &avg_ping, &min_ping, &max_ping, &GRDevice::_ping_calc_modifier);
+}
+
+void GRDevice::_update_avg_fps(uint64_t frametime) {
+ fps_queue.add_value_limited(frametime, (int)round(Engine::get_singleton()->get_frames_per_second()));
+ calculate_avg_min_max_values(fps_queue, &avg_fps, &min_fps, &max_fps, &GRDevice::_fps_calc_modifier);
+}
+
+float GRDevice::_ping_calc_modifier(double i) {
+ return float(i * 0.001);
+}
+
+float GRDevice::_fps_calc_modifier(double i) {
+ if (i > 0)
+ return float(1000000.0 / i);
+ else
+ return 0;
+}
+
+void GRDevice::send_user_data(Variant packet_id, Variant user_data, bool full_objects) {
+ Mutex_lock(send_queue_mutex);
+ Ref packet = newref(GRPacketCustomUserData);
+ send_packet(packet);
+
+ packet->set_packet_id(packet_id);
+ packet->set_send_full_objects(full_objects);
+ packet->set_user_data(user_data);
+
+ Mutex_unlock(send_queue_mutex);
+}
+
+void GRDevice::_send_queue_resize(int new_size) {
+ Mutex_lock(send_queue_mutex);
+ send_queue.resize(new_size);
+ Mutex_unlock(send_queue_mutex);
+}
+
+Ref GRDevice::_send_queue_pop_front() {
+ Mutex_lock(send_queue_mutex);
+ Ref packet;
+ if (send_queue.size() > 0) {
+ packet = send_queue.front();
+ send_queue.erase(send_queue.begin());
+ }
+ Mutex_unlock(send_queue_mutex);
+ return packet;
+}
+
+void GRDevice::set_status(WorkingStatus status) {
+ working_status = status;
+ emit_signal("status_changed", working_status);
+}
+
+float GRDevice::get_avg_ping() {
+ return avg_ping;
+}
+
+float GRDevice::get_min_ping() {
+ return min_ping;
+}
+
+float GRDevice::get_max_ping() {
+ return max_ping;
+}
+
+float GRDevice::get_avg_fps() {
+ return avg_fps;
+}
+
+float GRDevice::get_min_fps() {
+ return min_fps;
+}
+
+float GRDevice::get_max_fps() {
+ return max_fps;
+}
+
+uint16_t GRDevice::get_port() {
+ return port;
+}
+
+void GRDevice::set_port(uint16_t _port) {
+ port = _port;
+ restart();
+}
+
+void GRDevice::send_packet(Ref packet) {
+ ERR_FAIL_COND(packet.is_null());
+
+ Mutex_lock(send_queue_mutex);
+ if (send_queue.size() > 10000)
+ send_queue.resize(0);
+
+ send_queue.push_back(packet);
+ Mutex_unlock(send_queue_mutex);
+}
+
+void GRDevice::start() {
+ call_deferred("_internal_call_only_deffered_start");
+}
+
+void GRDevice::stop() {
+ call_deferred("_internal_call_only_deffered_stop");
+}
+
+void GRDevice::restart() {
+ call_deferred("_internal_call_only_deffered_restart");
+}
+
+void GRDevice::_internal_call_only_deffered_restart() {
+ if (get_status() == (int)WorkingStatus::STATUS_WORKING) {
+ _internal_call_only_deffered_stop();
+ _internal_call_only_deffered_start();
+ }
+}
+
+GRDevice::WorkingStatus GRDevice::get_status() {
+ return working_status;
+}
+
+void GRDevice::_init() {
+ LEAVE_IF_EDITOR();
+ port = GET_PS(GodotRemote::ps_general_port_name);
+
+ Mutex_delete(send_queue_mutex);
+ Mutex_create(send_queue_mutex);
+}
+
+void GRDevice::_deinit() {
+ LEAVE_IF_EDITOR();
+ Mutex_delete(send_queue_mutex);
+ if (GodotRemote::get_singleton()) {
+ GodotRemote::get_singleton()->device = nullptr;
+ }
+}
diff --git a/modules/godot_remote/godot_remote/GRDevice.h b/modules/godot_remote/godot_remote/GRDevice.h
new file mode 100644
index 0000000..5dd61ca
--- /dev/null
+++ b/modules/godot_remote/godot_remote/GRDevice.h
@@ -0,0 +1,143 @@
+/* GRDevice.h */
+#pragma once
+
+#include "GRInputData.h"
+#include "GRPacket.h"
+#include "GRUtils.h"
+
+#ifndef GDNATIVE_LIBRARY
+#include "scene/main/node.h"
+#else
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+using namespace godot;
+#endif
+
+class GRDevice : public Node {
+ GD_CLASS(GRDevice, Node);
+
+public:
+ enum class AuthResult : int {
+ OK = 0,
+ Error = 1,
+ Timeout = 2,
+ TryToConnect = 3,
+ RefuseConnection = 4,
+ VersionMismatch = 5,
+ IncorrectPassword = 6,
+ PasswordRequired = 7,
+ };
+
+ enum WorkingStatus : int {
+ STATUS_STOPPED = 0,
+ STATUS_WORKING = 1,
+ STATUS_STOPPING = 2,
+ STATUS_STARTING = 3,
+ };
+
+ enum TypesOfServerSettings : int {
+ SERVER_SETTINGS_USE_INTERNAL = 0,
+ SERVER_SETTINGS_VIDEO_STREAM_ENABLED = 1,
+ SERVER_SETTINGS_COMPRESSION_TYPE = 2,
+ SERVER_SETTINGS_JPG_QUALITY = 3,
+ SERVER_SETTINGS_SKIP_FRAMES = 4,
+ SERVER_SETTINGS_RENDER_SCALE = 5,
+ };
+
+ enum Subsampling : int {
+ SUBSAMPLING_Y_ONLY = 0,
+ SUBSAMPLING_H1V1 = 1,
+ SUBSAMPLING_H2V1 = 2,
+ SUBSAMPLING_H2V2 = 3
+ };
+
+ enum ImageCompressionType : int {
+ COMPRESSION_UNCOMPRESSED = 0,
+ COMPRESSION_JPG = 1,
+ COMPRESSION_PNG = 2,
+ };
+
+private:
+ WorkingStatus working_status = WorkingStatus::STATUS_STOPPED;
+
+protected:
+ template
+ T _find_queued_packet_by_type() {
+ for (int i = 0; i < send_queue.size(); i++) {
+ T o = send_queue[i];
+ if (o.is_valid()) {
+ return o;
+ }
+ }
+ return T();
+ }
+
+ GRUtils::iterable_queue fps_queue;
+ GRUtils::iterable_queue ping_queue;
+ float avg_ping = 0, min_ping = 0, max_ping = 0;
+ float avg_fps = 0, min_fps = 0, max_fps = 0;
+ uint32_t avg_ping_max_count = 100;
+
+ Mutex_define(send_queue_mutex);
+ std::vector][ > send_queue;
+
+ void set_status(WorkingStatus status);
+ void _update_avg_ping(uint64_t ping);
+ void _update_avg_fps(uint64_t frametime);
+ static float _ping_calc_modifier(double i);
+ static float _fps_calc_modifier(double i);
+ void _send_queue_resize(int new_size);
+ Ref _send_queue_pop_front();
+
+ virtual void _reset_counters();
+ virtual void _internal_call_only_deffered_start(){};
+ virtual void _internal_call_only_deffered_stop(){};
+
+#ifndef GDNATIVE_LIBRARY
+ static void _bind_methods();
+#else
+public:
+ static void _register_methods();
+
+protected:
+#endif
+
+ void _notification(int p_notification);
+
+public:
+ uint16_t port = 52341;
+
+ float get_avg_ping();
+ float get_min_ping();
+ float get_max_ping();
+ float get_avg_fps();
+ float get_min_fps();
+ float get_max_fps();
+ uint16_t get_port();
+ void set_port(uint16_t _port);
+
+ void send_packet(Ref packet);
+ void send_user_data(Variant packet_id, Variant user_data, bool full_objects = false);
+
+ void start();
+ void stop();
+ void restart();
+ void _internal_call_only_deffered_restart();
+
+ virtual WorkingStatus get_status();
+
+ void _init();
+ void _deinit();
+};
+
+#ifndef GDNATIVE_LIBRARY
+VARIANT_ENUM_CAST(GRDevice::WorkingStatus)
+VARIANT_ENUM_CAST(GRDevice::Subsampling)
+VARIANT_ENUM_CAST(GRDevice::ImageCompressionType)
+VARIANT_ENUM_CAST(GRDevice::TypesOfServerSettings)
+#endif
diff --git a/modules/godot_remote/godot_remote/GRInputData.cpp b/modules/godot_remote/godot_remote/GRInputData.cpp
new file mode 100644
index 0000000..bff312c
--- /dev/null
+++ b/modules/godot_remote/godot_remote/GRInputData.cpp
@@ -0,0 +1,461 @@
+/* GRInputData.cpp */
+#include "GRInputData.h"
+#include "GRPacket.h"
+
+#ifndef GDNATIVE_LIBRARY
+#include "core/os/os.h"
+#include "scene/main/scene_tree.h"
+#include "scene/main/viewport.h"
+#else
+
+#include
+#include
+#include
+using namespace godot;
+#endif
+
+using namespace GRUtils;
+
+void GRInputDeviceSensorsData::set_sensors(PoolVector3Array _sensors) {
+ data->resize(0);
+ data->put_8((uint8_t)get_type());
+ data->put_var(_sensors);
+}
+
+PoolVector3Array GRInputDeviceSensorsData::get_sensors() {
+ data->seek(0);
+ data->get_8();
+ return data->get_var();
+}
+
+Ref GRInputData::create(const PoolByteArray &buf) {
+#define CREATE(_d) \
+ { \
+ Ref<_d> id(memnew(_d)); \
+ id->data->set_data_array(buf); \
+ return id; \
+ }
+
+ InputType type = (InputType)((PoolByteArray)buf)[0];
+ switch (type) {
+ case InputType::_NoneIT:
+ ERR_PRINT("Can't create GRInputData with type 'None'!");
+ break;
+ // ADDITIONAL CLASSES
+ case InputType::_InputDeviceSensors:
+ CREATE(GRInputDeviceSensorsData);
+
+ // INPUT EVENTS
+ case InputType::_InputEvent:
+ case InputType::_InputEventWithModifiers:
+ case InputType::_InputEventMouse:
+ case InputType::_InputEventGesture:
+ ERR_PRINT("Can't create GRInputData for abstract InputEvent! Type index: " + str((int)type));
+ break;
+ case InputType::_InputEventAction:
+ CREATE(GRIEDataAction);
+ case InputType::_InputEventJoypadButton:
+ CREATE(GRIEDataJoypadButton);
+ case InputType::_InputEventJoypadMotion:
+ CREATE(GRIEDataJoypadMotion);
+ case InputType::_InputEventKey:
+ CREATE(GRIEDataKey);
+ case InputType::_InputEventMagnifyGesture:
+ CREATE(GRIEDataMagnifyGesture);
+ case InputType::_InputEventMIDI:
+ CREATE(GRIEDataMIDI);
+ case InputType::_InputEventMouseButton:
+ CREATE(GRIEDataMouseButton);
+ case InputType::_InputEventMouseMotion:
+ CREATE(GRIEDataMouseMotion);
+ case InputType::_InputEventPanGesture:
+ CREATE(GRIEDataPanGesture);
+ case InputType::_InputEventScreenDrag:
+ CREATE(GRIEDataScreenDrag);
+ case InputType::_InputEventScreenTouch:
+ CREATE(GRIEDataScreenTouch);
+ }
+#undef CREATE
+
+ ERR_PRINT("Can't create unsupported GRInputData! Type index: " + str((int)type));
+ return Ref();
+}
+
+Ref GRInputDataEvent::parse_event(const Ref &ev, const Rect2 &rect) {
+ if (ev.is_null())
+ ERR_FAIL_COND_V(ev.is_null(), Ref());
+
+#define PARSE(_i, _d) \
+ { \
+ Ref<_i> ie = ev; \
+ if (ie.is_valid()) { \
+ Ref<_d> data(memnew(_d)); \
+ data->_parse_event(ie, rect); \
+ return data; \
+ } \
+ }
+
+ PARSE(InputEventKey, GRIEDataKey);
+ PARSE(InputEventMouseButton, GRIEDataMouseButton);
+ PARSE(InputEventMouseMotion, GRIEDataMouseMotion);
+ PARSE(InputEventScreenTouch, GRIEDataScreenTouch);
+ PARSE(InputEventScreenDrag, GRIEDataScreenDrag);
+ PARSE(InputEventMagnifyGesture, GRIEDataMagnifyGesture);
+ PARSE(InputEventPanGesture, GRIEDataPanGesture);
+ PARSE(InputEventJoypadButton, GRIEDataJoypadButton);
+ PARSE(InputEventJoypadMotion, GRIEDataJoypadMotion);
+ PARSE(InputEventAction, GRIEDataAction);
+ PARSE(InputEventMIDI, GRIEDataMIDI);
+
+#undef PARSE
+
+ ERR_PRINT("Not supported InputEvent type: " + str(ev));
+ return Ref();
+}
+
+Ref GRInputDataEvent::construct_event(const Rect2 &rect) {
+ ERR_FAIL_COND_V(!data->get_size(), Ref());
+
+#define CONSTRUCT(_i) \
+ { \
+ Ref<_i> ev(memnew(_i)); \
+ return _construct_event(ev, vp_size); \
+ }
+
+ InputType type = _get_type();
+ ERR_FAIL_COND_V_MSG(type < InputType::_InputEvent || type >= InputType::_InputEventMAX, Ref(), "Not InputEvent");
+
+ Rect2 vp_size = rect;
+ if (vp_size.size.x == 0 && vp_size.size.y == 0 &&
+ vp_size.position.x == 0 && vp_size.position.y == 0) {
+ if (ST() && ST()->get_root()) {
+ //vp_size = SceneTree::get_singleton()->get_root()->get_visible_rect();
+ vp_size = Rect2(OS::get_singleton()->get_window_size(), ST()->get_root()->get_size());
+ }
+ }
+
+ switch (type) {
+ case InputType::_NoneIT:
+ ERR_PRINT("Can't create GRInputDataEvent with type 'None'!");
+ break;
+ case InputType::_InputEvent:
+ case InputType::_InputEventWithModifiers:
+ case InputType::_InputEventMouse:
+ case InputType::_InputEventGesture:
+ ERR_PRINT("Can't create GRInputDataEvent for abstract InputEvent! Type index: " + str((int)type));
+ break;
+ case InputType::_InputEventAction:
+ CONSTRUCT(InputEventAction);
+ case InputType::_InputEventJoypadButton:
+ CONSTRUCT(InputEventJoypadButton);
+ case InputType::_InputEventJoypadMotion:
+ CONSTRUCT(InputEventJoypadMotion);
+ case InputType::_InputEventKey:
+ CONSTRUCT(InputEventKey);
+ case InputType::_InputEventMagnifyGesture:
+ CONSTRUCT(InputEventMagnifyGesture);
+ case InputType::_InputEventMIDI:
+ CONSTRUCT(InputEventMIDI);
+ case InputType::_InputEventMouseButton:
+ CONSTRUCT(InputEventMouseButton);
+ case InputType::_InputEventMouseMotion:
+ CONSTRUCT(InputEventMouseMotion);
+ case InputType::_InputEventPanGesture:
+ CONSTRUCT(InputEventPanGesture);
+ case InputType::_InputEventScreenDrag:
+ CONSTRUCT(InputEventScreenDrag);
+ case InputType::_InputEventScreenTouch:
+ CONSTRUCT(InputEventScreenTouch);
+ }
+
+#undef CONSTRUCT
+
+ return Ref();
+}
+
+#define fix(_e) ((Vector2(_e) - rect.position) / rect.size)
+#define fix_rel(_e) (Vector2(_e) / rect.size)
+
+#define restore(_e) ((Vector2(_e) * rect.size) + ((rect.position - rect.size) / 2.f))
+#define restore_rel(_e) (Vector2(_e) * rect.size)
+
+#define CONSTRUCT(_type) Ref _type::_construct_event(Ref ev, const Rect2 &rect)
+#define PARSE(_type) void _type::_parse_event(const Ref &ev, const Rect2 &rect)
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventWithModifiers
+CONSTRUCT(GRIEDataWithModifiers) {
+ GRInputDataEvent::_construct_event(ev, rect);
+ Ref iewm = ev;
+ uint8_t flags = (uint8_t)data->get_8();
+ iewm->set_alt(flags & (1 << 0));
+ iewm->set_shift(flags & (1 << 1));
+ iewm->set_control(flags & (1 << 2));
+ iewm->set_metakey(flags & (1 << 3));
+ iewm->set_command(flags & (1 << 4));
+ return iewm;
+}
+
+PARSE(GRIEDataWithModifiers) {
+ GRInputDataEvent::_parse_event(ev, rect);
+ Ref iewm = ev;
+ data->put_8((uint8_t)iewm->get_alt() | (uint8_t)iewm->get_shift() << 1 | (uint8_t)iewm->get_control() << 2 |
+ (uint8_t)iewm->get_metakey() << 3 | (uint8_t)iewm->get_command() << 4);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventMouse
+CONSTRUCT(GRIEDataMouse) {
+ GRIEDataWithModifiers::_construct_event(ev, rect);
+ Ref iem = ev;
+ iem->set_button_mask(data->get_32());
+ iem->set_position(restore(data->get_var()));
+ iem->set_global_position(restore(data->get_var()));
+ return iem;
+}
+
+PARSE(GRIEDataMouse) {
+ GRIEDataWithModifiers::_parse_event(ev, rect);
+ Ref iem = ev;
+ data->put_32(iem->get_button_mask());
+ data->put_var(fix(iem->get_position()));
+ data->put_var(fix(iem->get_global_position()));
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventGesture
+CONSTRUCT(GRIEDataGesture) {
+ GRIEDataWithModifiers::_construct_event(ev, rect);
+ Ref ieg = ev;
+ ieg->set_position(restore(data->get_var()));
+ return ieg;
+}
+
+PARSE(GRIEDataGesture) {
+ GRIEDataWithModifiers::_parse_event(ev, rect);
+ Ref ieg = ev;
+ data->put_var(fix(ieg->get_position()));
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventKey
+CONSTRUCT(GRIEDataKey) {
+ GRIEDataWithModifiers::_construct_event(ev, rect);
+ Ref iek = ev;
+ uint8_t flags = (uint8_t)data->get_8();
+ iek->set_pressed(flags & 1);
+ iek->set_echo((flags >> 1) & 1);
+ iek->set_scancode(data->get_32());
+ iek->set_unicode(data->get_32());
+ return iek;
+}
+
+PARSE(GRIEDataKey) {
+ GRIEDataWithModifiers::_parse_event(ev, rect);
+ Ref iek = ev;
+ data->put_8((uint8_t)iek->is_pressed() | (uint8_t)iek->is_echo() << 1);
+ data->put_32(iek->get_scancode());
+ data->put_32(iek->get_unicode());
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventMouseButton
+CONSTRUCT(GRIEDataMouseButton) {
+ GRIEDataMouse::_construct_event(ev, rect);
+ Ref iemb = ev;
+ iemb->set_factor(data->get_float());
+ iemb->set_button_index(data->get_16());
+ uint8_t flags = (uint8_t)data->get_8();
+ iemb->set_pressed(flags & 1);
+ iemb->set_doubleclick((flags >> 1) & 1);
+ return iemb;
+}
+
+PARSE(GRIEDataMouseButton) {
+ GRIEDataMouse::_parse_event(ev, rect);
+ Ref iemb = ev;
+ data->put_float(iemb->get_factor());
+ data->put_16(iemb->get_button_index());
+ data->put_8((uint8_t)iemb->is_pressed() | (uint8_t)iemb->is_doubleclick() << 1);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventMouseMotion
+CONSTRUCT(GRIEDataMouseMotion) {
+ GRIEDataMouse::_construct_event(ev, rect);
+ Ref iemm = ev;
+ iemm->set_pressure(data->get_float());
+ iemm->set_tilt(data->get_var());
+ iemm->set_relative(restore_rel(data->get_var()));
+ iemm->set_speed(restore_rel(data->get_var()));
+ return iemm;
+}
+
+PARSE(GRIEDataMouseMotion) {
+ GRIEDataMouse::_parse_event(ev, rect);
+ Ref iemm = ev;
+ data->put_float(iemm->get_pressure());
+ data->put_var(iemm->get_tilt());
+ data->put_var(fix_rel(iemm->get_relative()));
+ data->put_var(fix_rel(iemm->get_speed()));
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventScreenTouch
+CONSTRUCT(GRIEDataScreenTouch) {
+ GRInputDataEvent::_construct_event(ev, rect);
+ Ref iest = ev;
+ iest->set_index(data->get_8());
+ iest->set_pressed(data->get_8());
+ iest->set_position(restore(data->get_var()));
+ return iest;
+}
+
+PARSE(GRIEDataScreenTouch) {
+ GRInputDataEvent::_parse_event(ev, rect);
+ Ref iest = ev;
+ data->put_8(iest->get_index());
+ data->put_8(iest->is_pressed());
+ data->put_var(fix(iest->get_position()));
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventScreenDrag
+CONSTRUCT(GRIEDataScreenDrag) {
+ GRInputDataEvent::_construct_event(ev, rect);
+ Ref iesd = ev;
+ iesd->set_index(data->get_8());
+ iesd->set_position(restore(data->get_var()));
+ iesd->set_relative(restore_rel(data->get_var()));
+ iesd->set_speed(restore_rel(data->get_var()));
+ return iesd;
+}
+
+PARSE(GRIEDataScreenDrag) {
+ GRInputDataEvent::_parse_event(ev, rect);
+ Ref iesd = ev;
+ data->put_8(iesd->get_index());
+ data->put_var(fix(iesd->get_position()));
+ data->put_var(fix_rel(iesd->get_relative()));
+ data->put_var(fix_rel(iesd->get_speed()));
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventMagnifyGesture
+CONSTRUCT(GRIEDataMagnifyGesture) {
+ GRIEDataGesture::_construct_event(ev, rect);
+ Ref iemg = ev;
+ iemg->set_factor(data->get_float());
+ return iemg;
+}
+
+PARSE(GRIEDataMagnifyGesture) {
+ GRIEDataGesture::_parse_event(ev, rect);
+ Ref iemg = ev;
+ data->put_float(iemg->get_factor());
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventPanGesture
+CONSTRUCT(GRIEDataPanGesture) {
+ GRIEDataGesture::_construct_event(ev, rect);
+ Ref iepg = ev;
+ iepg->set_delta(restore_rel(data->get_var()));
+ return iepg;
+}
+
+PARSE(GRIEDataPanGesture) {
+ GRIEDataGesture::_parse_event(ev, rect);
+ Ref iepg = ev;
+ data->put_var(fix_rel(iepg->get_delta()));
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventPanGesture
+CONSTRUCT(GRIEDataJoypadButton) {
+ GRInputDataEvent::_construct_event(ev, rect);
+ Ref iejb = ev;
+ iejb->set_button_index(data->get_32());
+ iejb->set_pressure(data->get_float());
+ iejb->set_pressed(data->get_8());
+ return iejb;
+}
+
+PARSE(GRIEDataJoypadButton) {
+ GRInputDataEvent::_parse_event(ev, rect);
+ Ref iejb = ev;
+ data->put_32(iejb->get_button_index());
+ data->put_float(iejb->get_pressure());
+ data->put_8(iejb->is_pressed());
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventJoypadMotion
+CONSTRUCT(GRIEDataJoypadMotion) {
+ GRInputDataEvent::_construct_event(ev, rect);
+ Ref iejm = ev;
+ iejm->set_axis(data->get_32());
+ iejm->set_axis_value(data->get_float());
+ return iejm;
+}
+
+PARSE(GRIEDataJoypadMotion) {
+ GRInputDataEvent::_parse_event(ev, rect);
+ Ref iejm = ev;
+ data->put_32(iejm->get_axis());
+ data->put_float(iejm->get_axis_value());
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventAction
+CONSTRUCT(GRIEDataAction) {
+ GRInputDataEvent::_construct_event(ev, rect);
+ Ref iea = ev;
+ iea->set_action(data->get_var());
+ iea->set_strength(data->get_float());
+ iea->set_pressed(data->get_8());
+ return iea;
+}
+
+PARSE(GRIEDataAction) {
+ GRInputDataEvent::_parse_event(ev, rect);
+ Ref iea = ev;
+ data->put_var(iea->get_action());
+ data->put_float(iea->get_strength());
+ data->put_8(iea->is_pressed());
+}
+
+//////////////////////////////////////////////////////////////////////////
+// InputEventAction
+CONSTRUCT(GRIEDataMIDI) {
+ GRInputDataEvent::_construct_event(ev, rect);
+ Ref iemidi = ev;
+ iemidi->set_channel(data->get_32());
+ iemidi->set_message(data->get_32());
+ iemidi->set_pitch(data->get_32());
+ iemidi->set_velocity(data->get_32());
+ iemidi->set_instrument(data->get_32());
+ iemidi->set_pressure(data->get_32());
+ iemidi->set_controller_number(data->get_32());
+ iemidi->set_controller_value(data->get_32());
+ return iemidi;
+}
+
+PARSE(GRIEDataMIDI) {
+ GRInputDataEvent::_parse_event(ev, rect);
+ Ref iemidi = ev;
+ data->put_32(iemidi->get_channel());
+ data->put_32(iemidi->get_message());
+ data->put_32(iemidi->get_pitch());
+ data->put_32(iemidi->get_velocity());
+ data->put_32(iemidi->get_instrument());
+ data->put_32(iemidi->get_pressure());
+ data->put_32(iemidi->get_controller_number());
+ data->put_32(iemidi->get_controller_value());
+}
+
+#undef fix
+#undef fix_rel
+#undef restore
+#undef CONSTRUCT
+#undef PARSE
diff --git a/modules/godot_remote/godot_remote/GRInputData.h b/modules/godot_remote/godot_remote/GRInputData.h
new file mode 100644
index 0000000..6dc364d
--- /dev/null
+++ b/modules/godot_remote/godot_remote/GRInputData.h
@@ -0,0 +1,213 @@
+/* GRInputData.h */
+#pragma once
+
+#include "GRUtils.h"
+#ifndef GDNATIVE_LIBRARY
+#include "core/io/stream_peer.h"
+#include "core/os/input_event.h"
+#include "core/reference.h"
+#else
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+using namespace godot;
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+// BASE CLASS
+
+// GodotRemoteInputData
+class GRInputData : public Reference {
+ GD_CLASS(GRInputData, Reference);
+ friend class GRInputDeviceSensorsData;
+
+public:
+ enum InputType : int {
+ _NoneIT = 0,
+ // Custom Input Data
+ _InputDeviceSensors = 1,
+
+ // Input Events
+ _InputEvent = 64,
+ _InputEventAction = 65,
+ _InputEventGesture = 66,
+ _InputEventJoypadButton = 67,
+ _InputEventJoypadMotion = 68,
+ _InputEventKey = 69,
+ _InputEventMagnifyGesture = 70,
+ _InputEventMIDI = 71,
+ _InputEventMouse = 72,
+ _InputEventMouseButton = 73,
+ _InputEventMouseMotion = 74,
+ _InputEventPanGesture = 75,
+ _InputEventScreenDrag = 76,
+ _InputEventScreenTouch = 77,
+ _InputEventWithModifiers = 78,
+ _InputEventMAX,
+ };
+
+protected:
+#ifndef GDNATIVE_LIBRARY
+ static void _bind_methods() {
+ BIND_ENUM_CONSTANT(_NoneIT);
+ BIND_ENUM_CONSTANT(_InputDeviceSensors);
+
+ BIND_ENUM_CONSTANT(_InputEvent);
+ BIND_ENUM_CONSTANT(_InputEventAction);
+ BIND_ENUM_CONSTANT(_InputEventGesture);
+ BIND_ENUM_CONSTANT(_InputEventJoypadButton);
+ BIND_ENUM_CONSTANT(_InputEventJoypadMotion);
+ BIND_ENUM_CONSTANT(_InputEventKey);
+ BIND_ENUM_CONSTANT(_InputEventMagnifyGesture);
+ BIND_ENUM_CONSTANT(_InputEventMIDI);
+ BIND_ENUM_CONSTANT(_InputEventMouse);
+ BIND_ENUM_CONSTANT(_InputEventMouseButton);
+ BIND_ENUM_CONSTANT(_InputEventMouseMotion);
+ BIND_ENUM_CONSTANT(_InputEventPanGesture);
+ BIND_ENUM_CONSTANT(_InputEventScreenDrag);
+ BIND_ENUM_CONSTANT(_InputEventScreenTouch);
+ BIND_ENUM_CONSTANT(_InputEventWithModifiers);
+ BIND_ENUM_CONSTANT(_InputEventMAX);
+ }
+#else
+public:
+ void _init(){};
+ static void _register_methods(){};
+protected:
+#endif
+
+ Ref data;
+ virtual InputType _get_type() { return InputType::_NoneIT; };
+
+public:
+ GRInputData() {
+ data = Ref(memnew(StreamPeerBuffer));
+ }
+
+ ~GRInputData() {
+ data->resize(0);
+ }
+
+ PoolByteArray get_data() {
+ return data->get_data_array();
+ }
+ void set_data(PoolByteArray &_data) {
+ data->set_data_array(_data);
+ }
+ virtual InputType get_type() {
+ if (data->get_size()) {
+ data->seek(0);
+ return (InputType)data->get_8();
+ } else {
+ return _get_type();
+ }
+ };
+ static Ref create(const PoolByteArray &buf);
+};
+
+//////////////////////////////////////////////////////////////////////////
+// TODO for now all custom classes must add first 8 bits for type
+// data->put_8((uint8_t)get_type())
+
+//////////////////////////////////////////////////////////////////////////
+// ADDITIONAL CLASSES
+
+// Device Sensors
+class GRInputDeviceSensorsData : public GRInputData {
+ GD_S_CLASS(GRInputDeviceSensorsData, GRInputData);
+
+protected:
+ GDNATIVE_BASIC_REGISTER;
+
+ virtual InputType _get_type() override { return InputType::_InputDeviceSensors; };
+
+public:
+ virtual void set_sensors(PoolVector3Array _sensors);
+ virtual PoolVector3Array get_sensors();
+};
+
+//////////////////////////////////////////////////////////////////////////
+// INPUT EVENTS
+
+// GodotRemoteInputEventData
+class GRInputDataEvent : public GRInputData {
+ GD_S_CLASS(GRInputDataEvent, GRInputData);
+
+protected:
+ GDNATIVE_BASIC_REGISTER;
+
+ virtual Ref _construct_event(Ref ev, const Rect2 &rect) {
+ data->seek(0);
+ data->get_8();
+ ev->set_device(data->get_32());
+ return data;
+ };
+ virtual void _parse_event(const Ref &ev, const Rect2 &rect) {
+ data->resize(0);
+ data->put_8((uint8_t)get_type());
+ data->put_32(ev->get_device());
+ };
+ virtual InputType _get_type() override { return InputType::_NoneIT; };
+
+public:
+ Ref construct_event(const Rect2 &rect = Rect2());
+ static Ref parse_event(const Ref &ev, const Rect2 &rect);
+};
+
+#define INPUT_EVENT_DATA(__class, _parent, _type) \
+ class __class : public _parent { \
+ GD_S_CLASS(__class, _parent); \
+ friend GRInputDataEvent; \
+ friend GRInputData; \
+ \
+ protected: \
+ GDNATIVE_BASIC_REGISTER; \
+ \
+ virtual Ref _construct_event(Ref ev, const Rect2 &rect) override; \
+ virtual void _parse_event(const Ref &ev, const Rect2 &rect) override; \
+ virtual InputType _get_type() override { return _type; }; \
+ \
+ public: \
+ }
+
+INPUT_EVENT_DATA(GRIEDataWithModifiers, GRInputDataEvent, InputType::_InputEventWithModifiers);
+INPUT_EVENT_DATA(GRIEDataMouse, GRIEDataWithModifiers, InputType::_InputEventMouse);
+INPUT_EVENT_DATA(GRIEDataGesture, GRIEDataWithModifiers, InputType::_InputEventGesture);
+
+INPUT_EVENT_DATA(GRIEDataKey, GRIEDataWithModifiers, InputType::_InputEventKey);
+INPUT_EVENT_DATA(GRIEDataMouseButton, GRIEDataMouse, InputType::_InputEventMouseButton);
+INPUT_EVENT_DATA(GRIEDataMouseMotion, GRIEDataMouse, InputType::_InputEventMouseMotion);
+INPUT_EVENT_DATA(GRIEDataScreenTouch, GRInputDataEvent, InputType::_InputEventScreenTouch);
+INPUT_EVENT_DATA(GRIEDataScreenDrag, GRInputDataEvent, InputType::_InputEventScreenDrag);
+INPUT_EVENT_DATA(GRIEDataMagnifyGesture, GRIEDataGesture, InputType::_InputEventMagnifyGesture);
+INPUT_EVENT_DATA(GRIEDataPanGesture, GRIEDataGesture, InputType::_InputEventPanGesture);
+INPUT_EVENT_DATA(GRIEDataJoypadButton, GRInputDataEvent, InputType::_InputEventJoypadButton);
+INPUT_EVENT_DATA(GRIEDataJoypadMotion, GRInputDataEvent, InputType::_InputEventJoypadMotion);
+INPUT_EVENT_DATA(GRIEDataAction, GRInputDataEvent, InputType::_InputEventAction);
+INPUT_EVENT_DATA(GRIEDataMIDI, GRInputDataEvent, InputType::_InputEventMIDI);
+
+#undef INPUT_EVENT_DATA
+
+#ifndef GDNATIVE_LIBRARY
+VARIANT_ENUM_CAST(GRInputData::InputType)
+#endif
diff --git a/modules/godot_remote/godot_remote/GRNotifications.cpp b/modules/godot_remote/godot_remote/GRNotifications.cpp
new file mode 100644
index 0000000..d615eae
--- /dev/null
+++ b/modules/godot_remote/godot_remote/GRNotifications.cpp
@@ -0,0 +1,1012 @@
+/* GRNotifications.cpp */
+#include "GRNotifications.h"
+#include "GRResources.h"
+#include "GRUtils.h"
+#include "GodotRemote.h"
+
+#ifndef GDNATIVE_LIBRARY
+
+#define add_s_override add_style_override
+#define set_b_icon set_icon
+
+#include "core/engine.h"
+#include "core/os/input_event.h"
+#include "scene/animation/tween.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/label.h"
+#include "scene/gui/texture_rect.h"
+#else
+
+#define add_s_override add_stylebox_override
+#define set_b_icon set_button_icon
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+using namespace godot;
+#endif
+
+using namespace GRUtils;
+
+GRNotifications *GRNotifications::singleton = nullptr;
+GRNotificationPanelSTATIC_DATA *GRNotificationPanel::_default_data = nullptr;
+
+std::vector GRNotifications::_get_notifications_with_title(String title) { // GRNotificationPanel *
+ std::vector res; // GRNotificationPanel *
+
+ if (singleton) {
+ for (int i = (int)singleton->notifications.size() - 1; i >= 0; i--) {
+ if (((GRNotificationPanel *)notifications[i])->get_title() == title) {
+ res.push_back(notifications[i]);
+ }
+ }
+ }
+ return res;
+}
+
+GRNotificationPanel *GRNotifications::_get_notification(String title) {
+ for (int i = (int)singleton->notifications.size() - 1; i >= 0; i--) {
+ if (((GRNotificationPanel *)notifications[i])->get_title() == title) {
+ return notifications[i];
+ }
+ }
+ return nullptr;
+}
+
+void GRNotifications::_set_notifications_position(ENUM_ARG(NotificationsPosition) positon) {
+ NotificationsPosition pos = (NotificationsPosition)positon;
+ if (notif_list_node) {
+ switch (pos) {
+ case NotificationsPosition::TOP_LEFT:
+ case NotificationsPosition::TOP_CENTER:
+ case NotificationsPosition::TOP_RIGHT:
+ notif_list_node->set_v_grow_direction(Control::GROW_DIRECTION_END);
+ notif_list_node->set_alignment(BoxContainer::ALIGN_BEGIN);
+ break;
+ case NotificationsPosition::BOTTOM_LEFT:
+ case NotificationsPosition::BOTTOM_CENTER:
+ case NotificationsPosition::BOTTOM_RIGHT:
+ notif_list_node->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN);
+ notif_list_node->set_alignment(BoxContainer::ALIGN_END);
+ break;
+ }
+ }
+
+ _set_all_notifications_positions(pos);
+ notifications_position = pos;
+}
+
+void GRNotifications::_add_notification_or_append_string(String title, String text, ENUM_ARG(NotificationIcon) icon, bool new_string, float duration_multiplier) {
+ if (!notifications_enabled)
+ return;
+
+ auto *np = _get_notification(title);
+ if (np) {
+ np->update_text(np->get_text() + (new_string ? "\n" + text : text));
+ } else {
+ _add_notification(title, text, icon, false, duration_multiplier);
+ }
+}
+
+void GRNotifications::_add_notification_or_update_line(String title, String id, String text, ENUM_ARG(NotificationIcon) icon, float duration_multiplier) {
+ if (!notifications_enabled)
+ return;
+
+ auto *np = cast_to(_get_notification(title));
+ if (np) {
+ _log("Updating existing updatable notification with Title: \"" + title + "\" ID: \"" + id + "\" Text:\"" + text + "\"", LogLevel::LL_DEBUG);
+ np->set_updatable_line(this, title, id, text, (NotificationIcon)icon, duration_multiplier, style);
+ } else {
+
+ np = memnew(GRNotificationPanelUpdatable);
+ notif_list_node->add_child(np);
+ if (notifications_position <= NotificationsPosition::TOP_RIGHT)
+ notif_list_node->move_child(np, 0);
+ notifications.push_back(np);
+
+ _log("New updatable notification added with Title: \"" + title + "\"" + " and Text:\"" + text + "\"", LogLevel::LL_DEBUG);
+ emit_signal("notification_added", title, text);
+
+ np->set_updatable_line(this, title, id, text, (NotificationIcon)icon, duration_multiplier, style);
+
+ // FORCE UPDATE SIZE OF CONTEINER
+ notif_list_node->call("_size_changed");
+ }
+}
+
+void GRNotifications::_set_all_notifications_positions(NotificationsPosition pos) {
+ for (int i = (int)singleton->notifications.size() - 1; i >= 0; i--) {
+ GRNotificationPanel *np = notifications[i];
+ if (np && !np->is_queued_for_deletion())
+ np->set_notification_position((NotificationsPosition)pos);
+ }
+}
+
+void GRNotifications::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_POSTINITIALIZE:
+#ifndef GDNATIVE_LIBRARY
+ _init();
+#endif
+ break;
+ case NOTIFICATION_PREDELETE:
+ _deinit();
+ break;
+ case NOTIFICATION_EXIT_TREE: {
+ GRNotificationPanel::clear_styles();
+ break;
+ }
+ }
+}
+
+#ifndef GDNATIVE_LIBRARY
+
+void GRNotifications::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_set_notifications_position", "pos"), &GRNotifications::_set_notifications_position);
+ ClassDB::bind_method(D_METHOD("_add_notification_or_append_string", "title", "text", "icon", "new_string", "duration_multiplier"), &GRNotifications::_add_notification_or_append_string);
+ ClassDB::bind_method(D_METHOD("_add_notification_or_update_line", "title", "id", "text", "icon", "duration_multiplier"), &GRNotifications::_add_notification_or_update_line);
+ ClassDB::bind_method(D_METHOD("_add_notification", "title", "text", "icon", "update_existing", "duration_multiplier"), &GRNotifications::_add_notification);
+ ClassDB::bind_method(D_METHOD("_remove_notification", "title", "is_all_entries"), &GRNotifications::_remove_notification);
+ ClassDB::bind_method(D_METHOD("_remove_exact_notification", "notification"), &GRNotifications::_remove_exact_notification);
+ ClassDB::bind_method(D_METHOD("_clear_notifications"), &GRNotifications::_clear_notifications);
+
+ ClassDB::bind_method(D_METHOD("_remove_list"), &GRNotifications::_remove_list);
+
+ ADD_SIGNAL(MethodInfo("notifications_toggled", PropertyInfo(Variant::BOOL, "is_enabled")));
+ ADD_SIGNAL(MethodInfo("notifications_cleared"));
+ ADD_SIGNAL(MethodInfo("notification_added", PropertyInfo(Variant::STRING, "title"), PropertyInfo(Variant::STRING, "text")));
+ ADD_SIGNAL(MethodInfo("notification_removed", PropertyInfo(Variant::STRING, "title"), PropertyInfo(Variant::BOOL, "is_cleared")));
+
+ BIND_ENUM_CONSTANT(ICON_NONE);
+ BIND_ENUM_CONSTANT(ICON_ERROR);
+ BIND_ENUM_CONSTANT(ICON_WARNING);
+ BIND_ENUM_CONSTANT(ICON_SUCCESS);
+ BIND_ENUM_CONSTANT(ICON_FAIL);
+
+ BIND_ENUM_CONSTANT(TOP_LEFT);
+ BIND_ENUM_CONSTANT(TOP_CENTER);
+ BIND_ENUM_CONSTANT(TOP_RIGHT);
+ BIND_ENUM_CONSTANT(BOTTOM_LEFT);
+ BIND_ENUM_CONSTANT(BOTTOM_CENTER);
+ BIND_ENUM_CONSTANT(BOTTOM_RIGHT);
+}
+
+#else
+
+void GRNotifications::_register_methods() {
+ METHOD_REG(GRNotifications, _notification);
+
+ METHOD_REG(GRNotifications, _set_notifications_position);
+ METHOD_REG(GRNotifications, _add_notification_or_append_string);
+ METHOD_REG(GRNotifications, _add_notification_or_update_line);
+ METHOD_REG(GRNotifications, _add_notification);
+ METHOD_REG(GRNotifications, _remove_notification);
+ METHOD_REG(GRNotifications, _remove_exact_notification);
+ METHOD_REG(GRNotifications, _clear_notifications);
+
+ METHOD_REG(GRNotifications, _remove_list);
+
+ register_signal("notifications_toggled", "is_enabled", GODOT_VARIANT_TYPE_BOOL);
+ register_signal("notifications_cleared", Dictionary::make());
+ register_signal("notification_added", "title", GODOT_VARIANT_TYPE_STRING, "text", GODOT_VARIANT_TYPE_STRING);
+ register_signal("notification_removed", "title", GODOT_VARIANT_TYPE_STRING, "is_cleared", GODOT_VARIANT_TYPE_BOOL);
+}
+
+#endif
+
+GRNotificationPanel *GRNotifications::get_notification(String title) {
+ if (singleton) {
+ return singleton->_get_notification(title);
+ }
+ return nullptr;
+}
+
+Array GRNotifications::get_all_notifications() {
+ Array arr;
+ if (singleton) {
+ for (int i = 0; i < singleton->notifications.size(); i++) {
+ arr.append(singleton->notifications[i]);
+ }
+ }
+ return arr;
+}
+
+Array GRNotifications::get_notifications_with_title(String title) {
+ Array arr;
+ if (singleton) {
+ auto list = singleton->_get_notifications_with_title(title);
+ for (int i = 0; i < list.size(); i++) {
+ arr.append(list[i]);
+ }
+ }
+ return arr;
+}
+
+GRNotifications::NotificationsPosition GRNotifications::get_notifications_position() {
+ if (singleton) {
+ return singleton->notifications_position;
+ }
+ return NotificationsPosition::TOP_CENTER;
+}
+
+void GRNotifications::set_notifications_position(NotificationsPosition positon) {
+ if (singleton) {
+ singleton->call_deferred("_set_notifications_position", positon);
+ }
+}
+
+bool GRNotifications::get_notifications_enabled() {
+ if (singleton)
+ return singleton->notifications_enabled;
+ return false;
+}
+
+void GRNotifications::set_notifications_enabled(bool _enabled) {
+ if (singleton) {
+ singleton->notifications_enabled = _enabled;
+ singleton->emit_signal("notifications_toggled", _enabled);
+ if (!_enabled) {
+ clear_notifications();
+ }
+ }
+}
+
+float GRNotifications::get_notifications_duration() {
+ if (singleton)
+ return singleton->notifications_duration;
+ return 0.f;
+}
+
+void GRNotifications::set_notifications_duration(float _duration) {
+ if (singleton)
+ singleton->notifications_duration = _duration;
+}
+
+Ref GRNotifications::get_notifications_style() {
+ if (singleton) {
+ if (singleton->style.is_valid())
+ return singleton->style;
+ return GRNotificationPanel::generate_default_style();
+ }
+ return Ref();
+}
+
+void GRNotifications::set_notifications_style(Ref _style) {
+ if (singleton) {
+ singleton->style = _style;
+ }
+}
+
+void GRNotifications::add_notification_or_append_string(String title, String text, NotificationIcon icon, bool new_string, float duration_multiplier) {
+ if (singleton) {
+ singleton->call_deferred("_add_notification_or_append_string", title, text, icon, new_string, duration_multiplier);
+ }
+}
+
+void GRNotifications::add_notification_or_update_line(String title, String id, String text, NotificationIcon icon, float duration_multiplier) {
+ if (singleton) {
+ singleton->call_deferred("_add_notification_or_update_line", title, id, text, icon, duration_multiplier);
+ }
+}
+
+void GRNotifications::add_notification(String title, String text, NotificationIcon icon, bool update_existing, float duration_multiplier) {
+ if (singleton) {
+ singleton->call_deferred("_add_notification", title, text, icon, update_existing, duration_multiplier);
+ }
+}
+
+void GRNotifications::remove_notification(String title, bool all_entries) {
+ if (singleton) {
+ singleton->call_deferred("_remove_notification", title, all_entries);
+ }
+}
+
+void GRNotifications::remove_notification_exact(Node *_notif) {
+ if (singleton) {
+ singleton->call_deferred("_remove_exact_notification", _notif);
+ }
+}
+
+void GRNotifications::clear_notifications() {
+ if (singleton) {
+ singleton->call_deferred("_clear_notifications");
+ }
+}
+
+void GRNotifications::_add_notification(String title, String text, ENUM_ARG(NotificationIcon) icon, bool update_existing, float duration_multiplier) {
+ if (!notifications_enabled)
+ return;
+
+ if (notif_list_node && !notif_list_node->is_queued_for_deletion()) {
+ GRNotificationPanel *np;
+ if (update_existing) {
+ np = _get_notification(title);
+ if (np) {
+ _log("Updating existing notification with Title: \"" + title + "\"" + " and Text:\"" + text + "\"", LogLevel::LL_DEBUG);
+ if (notifications_position <= NotificationsPosition::TOP_RIGHT) {
+ notif_list_node->move_child(np, 0);
+ } else {
+ notif_list_node->move_child(np, notif_list_node->get_child_count() - 1);
+ }
+ goto set_new_data;
+ }
+ }
+
+ np = memnew(GRNotificationPanel);
+ notif_list_node->add_child(np);
+ if (notifications_position <= NotificationsPosition::TOP_RIGHT)
+ notif_list_node->move_child(np, 0);
+ notifications.push_back(np);
+
+ _log("New notification added with Title: \"" + title + "\"" + " and Text:\"" + text + "\"", LogLevel::LL_DEBUG);
+ emit_signal("notification_added", title, text);
+
+ set_new_data:
+
+ np->set_data(this, title, text, (NotificationIcon)icon, duration_multiplier, style);
+
+ // FORCE UPDATE SIZE OF CONTEINER
+ notif_list_node->call("_size_changed");
+ }
+}
+
+void GRNotifications::_remove_notification(String title, bool all_entries) {
+ if (all_entries) {
+ auto nps = _get_notifications_with_title(title);
+ for (int i = 0; i < nps.size(); i++) {
+ _remove_exact_notification(nps[i]);
+ }
+ } else {
+ _remove_exact_notification(_get_notification(title));
+ }
+}
+
+void GRNotifications::_remove_exact_notification(Node *_notif) {
+ GRNotificationPanel *np = cast_to]