summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/arkit/SCsub8
-rw-r--r--modules/arkit/arkit_interface.mm1
-rw-r--r--modules/camera/SCsub22
-rw-r--r--modules/camera/camera_ios.h45
-rw-r--r--modules/camera/camera_ios.mm436
-rw-r--r--modules/camera/camera_osx.h47
-rw-r--r--modules/camera/camera_osx.mm362
-rw-r--r--modules/camera/camera_win.cpp98
-rw-r--r--modules/camera/camera_win.h46
-rw-r--r--modules/camera/config.py5
-rw-r--r--modules/camera/register_types.cpp56
-rw-r--r--modules/camera/register_types.h32
-rw-r--r--modules/gdnative/nativescript/nativescript.cpp5
-rw-r--r--modules/gdnative/pluginscript/pluginscript_script.cpp23
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml9
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml2
-rw-r--r--modules/gdscript/gdscript_parser.cpp7
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml3
-rw-r--r--modules/mono/build_scripts/mono_android_config.xml28
-rw-r--r--modules/mono/build_scripts/mono_configure.py7
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs47
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs2
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp7
-rw-r--r--modules/mono/editor/godotsharp_export.cpp25
-rw-r--r--modules/mono/editor/godotsharp_export.h5
-rw-r--r--modules/mono/glue/Managed/Files/Array.cs13
-rw-r--r--modules/mono/glue/Managed/Files/Dictionary.cs13
-rw-r--r--modules/mono/glue/collections_glue.cpp10
-rw-r--r--modules/mono/glue/collections_glue.h4
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp13
-rw-r--r--modules/mono/mono_gd/gd_mono_android.cpp601
-rw-r--r--modules/mono/mono_gd/gd_mono_android.h6
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h28
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.cpp2
-rw-r--r--modules/visual_script/visual_script_editor.cpp1
35 files changed, 1929 insertions, 90 deletions
diff --git a/modules/arkit/SCsub b/modules/arkit/SCsub
index b43d936768..e605703a72 100644
--- a/modules/arkit/SCsub
+++ b/modules/arkit/SCsub
@@ -5,6 +5,8 @@ Import('env_modules')
env_arkit = env_modules.Clone()
-# Add source files
-env_arkit.add_source_files(env.modules_sources, "*.cpp")
-env_arkit.add_source_files(env.modules_sources, "*.mm")
+# (iOS) Build as separate static library
+modules_sources = []
+env_arkit.add_source_files(modules_sources, "*.cpp")
+env_arkit.add_source_files(modules_sources, "*.mm")
+mod_lib = env_modules.add_library('#bin/libgodot_arkit_module' + env['LIBSUFFIX'], modules_sources) \ No newline at end of file
diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm
index 71642cfc30..3408477458 100644
--- a/modules/arkit/arkit_interface.mm
+++ b/modules/arkit/arkit_interface.mm
@@ -28,7 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "camera_ios.h"
#include "core/os/input.h"
#include "core/os/os.h"
#include "scene/resources/surface_tool.h"
diff --git a/modules/camera/SCsub b/modules/camera/SCsub
new file mode 100644
index 0000000000..23f031f06e
--- /dev/null
+++ b/modules/camera/SCsub
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+Import('env')
+Import('env_modules')
+
+env_camera = env_modules.Clone()
+
+if env["platform"] == "iphone":
+ # (iOS) Build as separate static library
+ modules_sources = []
+ env_camera.add_source_files(modules_sources, "register_types.cpp")
+ env_camera.add_source_files(modules_sources, "camera_ios.mm")
+ mod_lib = env_modules.add_library('#bin/libgodot_camera_module' + env['LIBSUFFIX'], modules_sources)
+
+elif env["platform"] == "windows":
+ env_camera.add_source_files(env.modules_sources, "register_types.cpp")
+ env_camera.add_source_files(env.modules_sources, "camera_win.cpp")
+
+elif env["platform"] == "osx":
+ env_camera.add_source_files(env.modules_sources, "register_types.cpp")
+ env_camera.add_source_files(env.modules_sources, "camera_osx.mm")
+
diff --git a/modules/camera/camera_ios.h b/modules/camera/camera_ios.h
new file mode 100644
index 0000000000..ceabdba6a3
--- /dev/null
+++ b/modules/camera/camera_ios.h
@@ -0,0 +1,45 @@
+/*************************************************************************/
+/* camera_ios.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* 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, and/or 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. */
+/*************************************************************************/
+
+#ifndef CAMERAIOS_H
+#define CAMERAIOS_H
+
+#include "servers/camera_server.h"
+
+class CameraIOS : public CameraServer {
+private:
+public:
+ CameraIOS();
+ ~CameraIOS();
+
+ void update_feeds();
+};
+
+#endif /* CAMERAIOS_H */ \ No newline at end of file
diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm
new file mode 100644
index 0000000000..dcf09b28fd
--- /dev/null
+++ b/modules/camera/camera_ios.mm
@@ -0,0 +1,436 @@
+/*************************************************************************/
+/* camera_ios.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* 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, and/or 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. */
+/*************************************************************************/
+
+///@TODO this is a near duplicate of CameraOSX, we should find a way to combine those to minimize code duplication!!!!
+// If you fix something here, make sure you fix it there as wel!
+
+#include "camera_ios.h"
+#include "servers/camera/camera_feed.h"
+
+#import <AVFoundation/AVFoundation.h>
+#import <UIKit/UIKit.h>
+
+//////////////////////////////////////////////////////////////////////////
+// MyCaptureSession - This is a little helper class so we can capture our frames
+
+@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
+ Ref<CameraFeed> feed;
+ size_t width[2];
+ size_t height[2];
+ PoolVector<uint8_t> img_data[2];
+
+ AVCaptureDeviceInput *input;
+ AVCaptureVideoDataOutput *output;
+}
+
+@end
+
+@implementation MyCaptureSession
+
+- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
+ if (self = [super init]) {
+ NSError *error;
+ feed = p_feed;
+ width[0] = 0;
+ height[0] = 0;
+ width[1] = 0;
+ height[1] = 0;
+
+ // prepare our device
+ [p_device lockForConfiguration:&error];
+
+ [p_device setFocusMode:AVCaptureFocusModeLocked];
+ [p_device setExposureMode:AVCaptureExposureModeLocked];
+ [p_device setWhiteBalanceMode:AVCaptureWhiteBalanceModeLocked];
+
+ [p_device unlockForConfiguration];
+
+ [self beginConfiguration];
+
+ // setup our capture
+ self.sessionPreset = AVCaptureSessionPreset1280x720;
+
+ input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
+ if (!input) {
+ print_line("Couldn't get input device for camera");
+ } else {
+ [self addInput:input];
+ }
+
+ output = [AVCaptureVideoDataOutput new];
+ if (!output) {
+ print_line("Couldn't get output device for camera");
+ } else {
+ NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
+ output.videoSettings = settings;
+
+ // discard if the data output queue is blocked (as we process the still image)
+ [output setAlwaysDiscardsLateVideoFrames:YES];
+
+ // now set ourselves as the delegate to receive new frames. Note that we're doing this on the main thread at the moment, we may need to change this..
+ [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
+
+ [self addOutput:output];
+ }
+
+ [self commitConfiguration];
+
+ // kick off our session..
+ [self startRunning];
+ };
+ return self;
+}
+
+- (void)cleanup {
+ // stop running
+ [self stopRunning];
+
+ // cleanup
+ [self beginConfiguration];
+
+ if (input) {
+ [self removeInput:input];
+ // don't release this
+ input = nil;
+ }
+
+ if (output) {
+ [self removeOutput:output];
+ [output setSampleBufferDelegate:nil queue:NULL];
+ [output release];
+ output = nil;
+ }
+
+ [self commitConfiguration];
+}
+
+- (void)dealloc {
+ // bye bye
+ [super dealloc];
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
+ // This gets called every time our camera has a new image for us to process.
+ // May need to investigate in a way to throttle this if we get more images then we're rendering frames..
+
+ // For now, version 1, we're just doing the bare minimum to make this work...
+
+ CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
+ // int width = CVPixelBufferGetWidth(pixelBuffer);
+ // int height = CVPixelBufferGetHeight(pixelBuffer);
+
+ // It says that we need to lock this on the documentation pages but it's not in the samples
+ // need to lock our base address so we can access our pixel buffers, better safe then sorry?
+ CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
+
+ // get our buffers
+ unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
+ unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
+ if (dataY == NULL) {
+ print_line("Couldn't access Y pixel buffer data");
+ } else if (dataCbCr == NULL) {
+ print_line("Couldn't access CbCr pixel buffer data");
+ } else {
+ UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+ Ref<Image> img[2];
+
+ {
+ // do Y
+ int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+ int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
+ int _bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+
+ if ((width[0] != new_width) || (height[0] != new_height)) {
+ // printf("Camera Y plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
+
+ width[0] = new_width;
+ height[0] = new_height;
+ img_data[0].resize(new_width * new_height);
+ }
+
+ PoolVector<uint8_t>::Write w = img_data[0].write();
+ memcpy(w.ptr(), dataY, new_width * new_height);
+
+ img[0].instance();
+ img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
+ }
+
+ {
+ // do CbCr
+ int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+ int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
+ int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
+
+ if ((width[1] != new_width) || (height[1] != new_height)) {
+ // printf("Camera CbCr plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
+
+ width[1] = new_width;
+ height[1] = new_height;
+ img_data[1].resize(2 * new_width * new_height);
+ }
+
+ PoolVector<uint8_t>::Write w = img_data[1].write();
+ memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+
+ ///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion
+ img[1].instance();
+ img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
+ }
+
+ // set our texture...
+ feed->set_YCbCr_imgs(img[0], img[1]);
+
+ // update our matrix to match the orientation, note, before changing anything
+ // here, be aware that the project orientation settings must match your xcode
+ // settings or this will go wrong!
+ Transform2D display_transform;
+ switch (orientation) {
+ case UIInterfaceOrientationPortrait: {
+ display_transform = Transform2D(0.0, -1.0, -1.0, 0.0, 1.0, 1.0);
+ } break;
+ case UIInterfaceOrientationLandscapeRight: {
+ display_transform = Transform2D(1.0, 0.0, 0.0, -1.0, 0.0, 1.0);
+ } break;
+ case UIInterfaceOrientationLandscapeLeft: {
+ display_transform = Transform2D(-1.0, 0.0, 0.0, 1.0, 1.0, 0.0);
+ } break;
+ default: {
+ display_transform = Transform2D(0.0, 1.0, 1.0, 0.0, 0.0, 0.0);
+ } break;
+ }
+
+ //TODO: this is correct for the camera on the back, I have a feeling this needs to be inversed for the camera on the front!
+ feed->set_transform(display_transform);
+ }
+
+ // and unlock
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
+}
+
+@end
+
+//////////////////////////////////////////////////////////////////////////
+// CameraFeedIOS - Subclass for camera feeds in iOS
+
+class CameraFeedIOS : public CameraFeed {
+private:
+ AVCaptureDevice *device;
+ MyCaptureSession *capture_session;
+
+public:
+ bool get_is_arkit() const;
+ AVCaptureDevice *get_device() const;
+
+ CameraFeedIOS();
+ ~CameraFeedIOS();
+
+ void set_device(AVCaptureDevice *p_device);
+
+ bool activate_feed();
+ void deactivate_feed();
+};
+
+AVCaptureDevice *CameraFeedIOS::get_device() const {
+ return device;
+};
+
+CameraFeedIOS::CameraFeedIOS() {
+ capture_session = NULL;
+ device = NULL;
+ transform = Transform2D(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); /* should re-orientate this based on device orientation */
+};
+
+void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
+ device = p_device;
+ [device retain];
+
+ // get some info
+ NSString *device_name = p_device.localizedName;
+ name = device_name.UTF8String;
+ position = CameraFeed::FEED_UNSPECIFIED;
+ if ([p_device position] == AVCaptureDevicePositionBack) {
+ position = CameraFeed::FEED_BACK;
+ } else if ([p_device position] == AVCaptureDevicePositionFront) {
+ position = CameraFeed::FEED_FRONT;
+ };
+};
+
+CameraFeedIOS::~CameraFeedIOS() {
+ if (capture_session != NULL) {
+ [capture_session release];
+ capture_session = NULL;
+ };
+
+ if (device != NULL) {
+ [device release];
+ device = NULL;
+ };
+};
+
+bool CameraFeedIOS::activate_feed() {
+ if (capture_session) {
+ // already recording!
+ } else {
+ // start camera capture
+ capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
+ };
+
+ return true;
+};
+
+void CameraFeedIOS::deactivate_feed() {
+ // end camera capture if we have one
+ if (capture_session) {
+ [capture_session cleanup];
+ [capture_session release];
+ capture_session = NULL;
+ };
+};
+
+//////////////////////////////////////////////////////////////////////////
+// MyDeviceNotifications - This is a little helper class gets notifications
+// when devices are connected/disconnected
+
+@interface MyDeviceNotifications : NSObject {
+ CameraIOS *camera_server;
+}
+
+@end
+
+@implementation MyDeviceNotifications
+
+- (void)devices_changed:(NSNotification *)notification {
+ camera_server->update_feeds();
+}
+
+- (id)initForServer:(CameraIOS *)p_server {
+ if (self = [super init]) {
+ camera_server = p_server;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
+ };
+ return self;
+}
+
+- (void)dealloc {
+ // remove notifications
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
+
+ [super dealloc];
+}
+
+@end
+
+MyDeviceNotifications *device_notifications = nil;
+
+//////////////////////////////////////////////////////////////////////////
+// CameraIOS - Subclass for our camera server on iPhone
+
+void CameraIOS::update_feeds() {
+ // this way of doing things is deprecated but still works,
+ // rewrite to using AVCaptureDeviceDiscoverySession
+
+ AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeBuiltInTelephotoCamera, AVCaptureDeviceTypeBuiltInDualCamera, AVCaptureDeviceTypeBuiltInTrueDepthCamera, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
+
+ // remove devices that are gone..
+ for (int i = feeds.size() - 1; i >= 0; i--) {
+ Ref<CameraFeedIOS> feed(feeds[i]);
+
+ if (feed.is_null()) {
+ // feed not managed by us
+ } else if (![session.devices containsObject:feed->get_device()]) {
+ // remove it from our array, this will also destroy it ;)
+ remove_feed(feed);
+ };
+ };
+
+ // add new devices..
+ for (AVCaptureDevice *device in session.devices) {
+ bool found = false;
+
+ for (int i = 0; i < feeds.size() && !found; i++) {
+ Ref<CameraFeedIOS> feed(feeds[i]);
+
+ if (feed.is_null()) {
+ // feed not managed by us
+ } else if (feed->get_device() == device) {
+ found = true;
+ };
+ };
+
+ if (!found) {
+ Ref<CameraFeedIOS> newfeed;
+ newfeed.instance();
+ newfeed->set_device(device);
+ add_feed(newfeed);
+ };
+ };
+};
+
+CameraIOS::CameraIOS() {
+ // check if we have our usage description
+ NSString *usage_desc = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSCameraUsageDescription"];
+ if (usage_desc == NULL) {
+ // don't initialise if we don't get anything
+ print_line("No NSCameraUsageDescription key in pList, no access to cameras.");
+ return;
+ } else if (usage_desc.length == 0) {
+ // don't initialise if we don't get anything
+ print_line("Empty NSCameraUsageDescription key in pList, no access to cameras.");
+ return;
+ }
+
+ // now we'll request access.
+ // If this is the first time the user will be prompted with the string (iOS will read it).
+ // Once a decision is made it is returned. If the user wants to change it later on they
+ // need to go into setting.
+ print_line("Requesting Camera permissions");
+
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
+ completionHandler:^(BOOL granted) {
+ if (granted) {
+ print_line("Access to cameras granted!");
+
+ // Find available cameras we have at this time
+ update_feeds();
+
+ // should only have one of these....
+ device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
+ } else {
+ print_line("No access to cameras!");
+ }
+ }];
+};
+
+CameraIOS::~CameraIOS() {
+ [device_notifications release];
+};
diff --git a/modules/camera/camera_osx.h b/modules/camera/camera_osx.h
new file mode 100644
index 0000000000..7477d8e647
--- /dev/null
+++ b/modules/camera/camera_osx.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* camera_osx.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* 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, and/or 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. */
+/*************************************************************************/
+
+#ifndef CAMERAOSX_H
+#define CAMERAOSX_H
+
+///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
+// If you fix something here, make sure you fix it there as wel!
+
+#include "servers/camera_server.h"
+
+class CameraOSX : public CameraServer {
+public:
+ CameraOSX();
+ ~CameraOSX();
+
+ void update_feeds();
+};
+
+#endif /* CAMERAOSX_H */
diff --git a/modules/camera/camera_osx.mm b/modules/camera/camera_osx.mm
new file mode 100644
index 0000000000..2b0f4906fc
--- /dev/null
+++ b/modules/camera/camera_osx.mm
@@ -0,0 +1,362 @@
+/*************************************************************************/
+/* camera_osx.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* 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, and/or 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. */
+/*************************************************************************/
+
+///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
+// If you fix something here, make sure you fix it there as wel!
+
+#include "camera_osx.h"
+#include "servers/camera/camera_feed.h"
+#import <AVFoundation/AVFoundation.h>
+
+//////////////////////////////////////////////////////////////////////////
+// MyCaptureSession - This is a little helper class so we can capture our frames
+
+@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
+ Ref<CameraFeed> feed;
+ size_t width[2];
+ size_t height[2];
+ PoolVector<uint8_t> img_data[2];
+
+ AVCaptureDeviceInput *input;
+ AVCaptureVideoDataOutput *output;
+}
+
+@end
+
+@implementation MyCaptureSession
+
+- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
+ if (self = [super init]) {
+ NSError *error;
+ feed = p_feed;
+ width[0] = 0;
+ height[0] = 0;
+ width[1] = 0;
+ height[1] = 0;
+
+ [self beginConfiguration];
+
+ input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
+ if (!input) {
+ print_line("Couldn't get input device for camera");
+ } else {
+ [self addInput:input];
+ }
+
+ output = [AVCaptureVideoDataOutput new];
+ if (!output) {
+ print_line("Couldn't get output device for camera");
+ } else {
+ NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
+ output.videoSettings = settings;
+
+ // discard if the data output queue is blocked (as we process the still image)
+ [output setAlwaysDiscardsLateVideoFrames:YES];
+
+ // now set ourselves as the delegate to receive new frames.
+ [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
+
+ // this takes ownership
+ [self addOutput:output];
+ }
+
+ [self commitConfiguration];
+
+ // kick off our session..
+ [self startRunning];
+ };
+ return self;
+}
+
+- (void)cleanup {
+ // stop running
+ [self stopRunning];
+
+ // cleanup
+ [self beginConfiguration];
+
+ // remove input
+ if (input) {
+ [self removeInput:input];
+ // don't release this
+ input = NULL;
+ }
+
+ // free up our output
+ if (output) {
+ [self removeOutput:output];
+ [output setSampleBufferDelegate:nil queue:NULL];
+ [output release];
+ output = NULL;
+ }
+
+ [self commitConfiguration];
+}
+
+- (void)dealloc {
+ // bye bye
+ [super dealloc];
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
+ // This gets called every time our camera has a new image for us to process.
+ // May need to investigate in a way to throttle this if we get more images then we're rendering frames..
+
+ // For now, version 1, we're just doing the bare minimum to make this work...
+ CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
+ // int _width = CVPixelBufferGetWidth(pixelBuffer);
+ // int _height = CVPixelBufferGetHeight(pixelBuffer);
+
+ // It says that we need to lock this on the documentation pages but it's not in the samples
+ // need to lock our base address so we can access our pixel buffers, better safe then sorry?
+ CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
+
+ // get our buffers
+ unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
+ unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
+ if (dataY == NULL) {
+ print_line("Couldn't access Y pixel buffer data");
+ } else if (dataCbCr == NULL) {
+ print_line("Couldn't access CbCr pixel buffer data");
+ } else {
+ Ref<Image> img[2];
+
+ {
+ // do Y
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
+
+ if ((width[0] != new_width) || (height[0] != new_height)) {
+ width[0] = new_width;
+ height[0] = new_height;
+ img_data[0].resize(new_width * new_height);
+ }
+
+ PoolVector<uint8_t>::Write w = img_data[0].write();
+ memcpy(w.ptr(), dataY, new_width * new_height);
+
+ img[0].instance();
+ img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
+ }
+
+ {
+ // do CbCr
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
+
+ if ((width[1] != new_width) || (height[1] != new_height)) {
+ width[1] = new_width;
+ height[1] = new_height;
+ img_data[1].resize(2 * new_width * new_height);
+ }
+
+ PoolVector<uint8_t>::Write w = img_data[1].write();
+ memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+
+ ///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion
+ img[1].instance();
+ img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
+ }
+
+ // set our texture...
+ feed->set_YCbCr_imgs(img[0], img[1]);
+ }
+
+ // and unlock
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
+}
+
+@end
+
+//////////////////////////////////////////////////////////////////////////
+// CameraFeedOSX - Subclass for camera feeds in OSX
+
+class CameraFeedOSX : public CameraFeed {
+private:
+ AVCaptureDevice *device;
+ MyCaptureSession *capture_session;
+
+public:
+ AVCaptureDevice *get_device() const;
+
+ CameraFeedOSX();
+ ~CameraFeedOSX();
+
+ void set_device(AVCaptureDevice *p_device);
+
+ bool activate_feed();
+ void deactivate_feed();
+};
+
+AVCaptureDevice *CameraFeedOSX::get_device() const {
+ return device;
+};
+
+CameraFeedOSX::CameraFeedOSX() {
+ device = NULL;
+ capture_session = NULL;
+};
+
+void CameraFeedOSX::set_device(AVCaptureDevice *p_device) {
+ device = p_device;
+ [device retain];
+
+ // get some info
+ NSString *device_name = p_device.localizedName;
+ name = device_name.UTF8String;
+ position = CameraFeed::FEED_UNSPECIFIED;
+ if ([p_device position] == AVCaptureDevicePositionBack) {
+ position = CameraFeed::FEED_BACK;
+ } else if ([p_device position] == AVCaptureDevicePositionFront) {
+ position = CameraFeed::FEED_FRONT;
+ };
+};
+
+CameraFeedOSX::~CameraFeedOSX() {
+ if (capture_session != NULL) {
+ [capture_session release];
+ capture_session = NULL;
+ };
+
+ if (device != NULL) {
+ [device release];
+ device = NULL;
+ };
+};
+
+bool CameraFeedOSX::activate_feed() {
+ if (capture_session) {
+ // already recording!
+ } else {
+ // start camera capture
+ capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
+ };
+
+ return true;
+};
+
+void CameraFeedOSX::deactivate_feed() {
+ // end camera capture if we have one
+ if (capture_session) {
+ [capture_session cleanup];
+ [capture_session release];
+ capture_session = NULL;
+ };
+};
+
+//////////////////////////////////////////////////////////////////////////
+// MyDeviceNotifications - This is a little helper class gets notifications
+// when devices are connected/disconnected
+
+@interface MyDeviceNotifications : NSObject {
+ CameraOSX *camera_server;
+}
+
+@end
+
+@implementation MyDeviceNotifications
+
+- (void)devices_changed:(NSNotification *)notification {
+ camera_server->update_feeds();
+}
+
+- (id)initForServer:(CameraOSX *)p_server {
+ if (self = [super init]) {
+ camera_server = p_server;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
+ };
+ return self;
+}
+
+- (void)dealloc {
+ // remove notifications
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
+
+ [super dealloc];
+}
+
+@end
+
+MyDeviceNotifications *device_notifications = nil;
+
+//////////////////////////////////////////////////////////////////////////
+// CameraOSX - Subclass for our camera server on OSX
+
+void CameraOSX::update_feeds() {
+ NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
+
+ // remove devices that are gone..
+ for (int i = feeds.size() - 1; i >= 0; i--) {
+ Ref<CameraFeedOSX> feed = (Ref<CameraFeedOSX>)feeds[i];
+
+ if (![devices containsObject:feed->get_device()]) {
+ // remove it from our array, this will also destroy it ;)
+ remove_feed(feed);
+ };
+ };
+
+ // add new devices..
+ for (AVCaptureDevice *device in devices) {
+ bool found = false;
+ for (int i = 0; i < feeds.size() && !found; i++) {
+ Ref<CameraFeedOSX> feed = (Ref<CameraFeedOSX>)feeds[i];
+ if (feed->get_device() == device) {
+ found = true;
+ };
+ };
+
+ if (!found) {
+ Ref<CameraFeedOSX> newfeed;
+ newfeed.instance();
+ newfeed->set_device(device);
+
+ // assume display camera so inverse
+ Transform2D transform = Transform2D(-1.0, 0.0, 0.0, -1.0, 1.0, 1.0);
+ newfeed->set_transform(transform);
+
+ add_feed(newfeed);
+ };
+ };
+};
+
+CameraOSX::CameraOSX() {
+ // Find available cameras we have at this time
+ update_feeds();
+
+ // should only have one of these....
+ device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
+};
+
+CameraOSX::~CameraOSX() {
+ [device_notifications release];
+};
diff --git a/modules/camera/camera_win.cpp b/modules/camera/camera_win.cpp
new file mode 100644
index 0000000000..10787d0d0a
--- /dev/null
+++ b/modules/camera/camera_win.cpp
@@ -0,0 +1,98 @@
+/*************************************************************************/
+/* camera_win.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* 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, and/or 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. */
+/*************************************************************************/
+
+#include "camera_win.h"
+
+///@TODO sorry guys, I got about 80% through implementing this using DirectShow only
+// to find out Microsoft deprecated half the API and its replacement is as confusing
+// as they could make it. Joey suggested looking into libuvc which offers a more direct
+// route to webcams over USB and this is very promising but it wouldn't compile on
+// windows for me...I've gutted the classes I implemented DirectShow in just to have
+// a skeleton for someone to work on, mail me for more details or if you want a copy....
+
+//////////////////////////////////////////////////////////////////////////
+// CameraFeedWindows - Subclass for our camera feed on windows
+
+/// @TODO need to implement this
+
+class CameraFeedWindows : public CameraFeed {
+private:
+protected:
+public:
+ CameraFeedWindows();
+ virtual ~CameraFeedWindows();
+
+ bool activate_feed();
+ void deactivate_feed();
+};
+
+CameraFeedWindows::CameraFeedWindows(){
+ ///@TODO implement this, should store information about our available camera
+};
+
+CameraFeedWindows::~CameraFeedWindows() {
+ // make sure we stop recording if we are!
+ if (is_active()) {
+ deactivate_feed();
+ };
+
+ ///@TODO free up anything used by this
+};
+
+bool CameraFeedWindows::activate_feed() {
+ ///@TODO this should activate our camera and start the process of capturing frames
+
+ return true;
+};
+
+///@TODO we should probably have a callback method here that is being called by the
+// camera API which provides frames and call back into the CameraServer to update our texture
+
+void CameraFeedWindows::deactivate_feed(){
+ ///@TODO this should deactivate our camera and stop the process of capturing frames
+};
+
+//////////////////////////////////////////////////////////////////////////
+// CameraWindows - Subclass for our camera server on windows
+
+void CameraWindows::add_active_cameras(){
+ ///@TODO scan through any active cameras and create CameraFeedWindows objects for them
+};
+
+CameraWindows::CameraWindows() {
+ // Find cameras active right now
+ add_active_cameras();
+
+ // need to add something that will react to devices being connected/removed...
+};
+
+CameraWindows::~CameraWindows(){
+
+};
diff --git a/modules/camera/camera_win.h b/modules/camera/camera_win.h
new file mode 100644
index 0000000000..22ce9aa43f
--- /dev/null
+++ b/modules/camera/camera_win.h
@@ -0,0 +1,46 @@
+/*************************************************************************/
+/* camera_win.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* 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, and/or 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. */
+/*************************************************************************/
+
+#ifndef CAMERAWIN_H
+#define CAMERAWIN_H
+
+#include "servers/camera/camera_feed.h"
+#include "servers/camera_server.h"
+
+class CameraWindows : public CameraServer {
+private:
+ void add_active_cameras();
+
+public:
+ CameraWindows();
+ ~CameraWindows();
+};
+
+#endif /* CAMERAWIN_H */
diff --git a/modules/camera/config.py b/modules/camera/config.py
new file mode 100644
index 0000000000..d308c04195
--- /dev/null
+++ b/modules/camera/config.py
@@ -0,0 +1,5 @@
+def can_build(env, platform):
+ return platform == 'iphone' or platform == 'osx' or platform == 'windows'
+
+def configure(env):
+ pass
diff --git a/modules/camera/register_types.cpp b/modules/camera/register_types.cpp
new file mode 100644
index 0000000000..313df40112
--- /dev/null
+++ b/modules/camera/register_types.cpp
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* register_types.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* 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, and/or 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. */
+/*************************************************************************/
+
+#include "register_types.h"
+
+#if defined(WINDOWS_ENABLED)
+#include "camera_win.h"
+#endif
+#if defined(IPHONE_ENABLED)
+#include "camera_ios.h"
+#endif
+#if defined(OSX_ENABLED)
+#include "camera_osx.h"
+#endif
+
+void register_camera_types() {
+#if defined(WINDOWS_ENABLED)
+ CameraServer::make_default<CameraWindows>();
+#endif
+#if defined(IPHONE_ENABLED)
+ CameraServer::make_default<CameraIOS>();
+#endif
+#if defined(OSX_ENABLED)
+ CameraServer::make_default<CameraOSX>();
+#endif
+}
+
+void unregister_camera_types() {
+}
diff --git a/modules/camera/register_types.h b/modules/camera/register_types.h
new file mode 100644
index 0000000000..0ccb0885d0
--- /dev/null
+++ b/modules/camera/register_types.h
@@ -0,0 +1,32 @@
+/*************************************************************************/
+/* register_types.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* 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, and/or 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. */
+/*************************************************************************/
+
+void register_camera_types();
+void unregister_camera_types();
diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp
index 7c313c983f..768b12baea 100644
--- a/modules/gdnative/nativescript/nativescript.cpp
+++ b/modules/gdnative/nativescript/nativescript.cpp
@@ -119,7 +119,10 @@ String NativeScript::get_class_name() const {
void NativeScript::set_library(Ref<GDNativeLibrary> p_library) {
if (!library.is_null()) {
- WARN_PRINT("library on NativeScript already set. Do nothing.");
+ WARN_PRINT("Library in NativeScript already set. Do nothing.");
+ return;
+ }
+ if (p_library.is_null()) {
return;
}
library = p_library;
diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp
index f7c961d38b..6bb521173f 100644
--- a/modules/gdnative/pluginscript/pluginscript_script.cpp
+++ b/modules/gdnative/pluginscript/pluginscript_script.cpp
@@ -251,7 +251,19 @@ Error PluginScript::reload(bool p_keep_state) {
(godot_string *)&_path,
(godot_string *)&_source,
(godot_error *)&err);
+// Manifest's attributes must be explicitly freed
+#define FREE_SCRIPT_MANIFEST(manifest) \
+ { \
+ godot_string_name_destroy(&manifest.name); \
+ godot_string_name_destroy(&manifest.base); \
+ godot_dictionary_destroy(&manifest.member_lines); \
+ godot_array_destroy(&manifest.methods); \
+ godot_array_destroy(&manifest.signals); \
+ godot_array_destroy(&manifest.properties); \
+ }
+
if (err) {
+ FREE_SCRIPT_MANIFEST(manifest);
// TODO: GDscript uses `ScriptDebugger` here to jump into the parsing error
return err;
}
@@ -269,6 +281,7 @@ Error PluginScript::reload(bool p_keep_state) {
_ref_base_parent = res;
} else {
String name = *(StringName *)&manifest.name;
+ FREE_SCRIPT_MANIFEST(manifest);
ERR_FAIL_V_MSG(ERR_PARSE_ERROR, _path + ": Script '" + name + "' has an invalid parent '" + *base_name + "'.");
}
}
@@ -317,13 +330,6 @@ Error PluginScript::reload(bool p_keep_state) {
_methods_rpc_mode[pi.name] = MultiplayerAPI::RPCMode(int(var));
}
}
- // Manifest's attributes must be explicitly freed
- godot_string_name_destroy(&manifest.name);
- godot_string_name_destroy(&manifest.base);
- godot_dictionary_destroy(&manifest.member_lines);
- godot_array_destroy(&manifest.methods);
- godot_array_destroy(&manifest.signals);
- godot_array_destroy(&manifest.properties);
#ifdef TOOLS_ENABLED
/*for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) {
@@ -331,7 +337,10 @@ Error PluginScript::reload(bool p_keep_state) {
_update_placeholder(E->get());
}*/
#endif
+
+ FREE_SCRIPT_MANIFEST(manifest);
return OK;
+#undef FREE_SCRIPT_MANIFEST
}
void PluginScript::get_script_method_list(List<MethodInfo> *r_methods) const {
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index a22d18b970..2a8453116e 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -336,11 +336,12 @@
<description>
Rounds [code]s[/code] to the closest smaller integer and returns it.
[codeblock]
- # a is 2
+ # a is 2.0
a = floor(2.99)
- # a is -3
+ # a is -3.0
a = floor(-2.99)
[/codeblock]
+ [b]Note:[/b] This method returns a float. If you need an integer, you can use [code]int(s)[/code] directly.
</description>
</method>
<method name="fmod">
@@ -502,7 +503,7 @@
<argument index="1" name="b" type="float">
</argument>
<description>
- Returns True/False whether [code]a[/code] and [code]b[/code] are approximately equal to each other.
+ Returns [code]true[/code] if [code]a[/code] and [code]b[/code] are approximately equal to each other.
</description>
</method>
<method name="is_inf">
@@ -538,7 +539,7 @@
<argument index="0" name="s" type="float">
</argument>
<description>
- Returns True/False whether [code]s[/code] is zero or almost zero.
+ Returns [code]true[/code] if [code]s[/code] is zero or almost zero.
</description>
</method>
<method name="len">
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 6f43361914..8e175a7ab8 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -4,7 +4,7 @@
A script implemented in the GDScript programming language.
</brief_description>
<description>
- A script implemented in the GDScript programming language. The script exends the functionality of all objects that instance it.
+ A script implemented in the GDScript programming language. The script extends the functionality of all objects that instance it.
[method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes.
</description>
<tutorials>
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 6ef3ab67ae..ef1a282e51 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3349,7 +3349,12 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
}
p_block->statements.push_back(expression);
if (!_end_statement()) {
- _set_error("Expected end of statement after expression.");
+ // Attempt to guess a better error message if the user "retypes" a variable
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ _set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression.");
+ } else {
+ _set_error(String() + "Expected end of statement after expression, got " + tokenizer->get_token_name(tokenizer->get_token()) + " instead");
+ }
return;
}
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index a140fc8ac6..3de971db6d 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -226,9 +226,6 @@
<member name="mesh_library" type="MeshLibrary" setter="set_mesh_library" getter="get_mesh_library">
The assigned [MeshLibrary].
</member>
- <member name="theme" type="MeshLibrary" setter="set_theme" getter="get_theme">
- Deprecated, use [member mesh_library] instead.
- </member>
</members>
<signals>
<signal name="cell_size_changed">
diff --git a/modules/mono/build_scripts/mono_android_config.xml b/modules/mono/build_scripts/mono_android_config.xml
new file mode 100644
index 0000000000..e79670afd2
--- /dev/null
+++ b/modules/mono/build_scripts/mono_android_config.xml
@@ -0,0 +1,28 @@
+<configuration>
+ <dllmap wordsize="32" dll="i:cygwin1.dll" target="/system/lib/libc.so" />
+ <dllmap wordsize="64" dll="i:cygwin1.dll" target="/system/lib64/libc.so" />
+ <dllmap wordsize="32" dll="libc" target="/system/lib/libc.so" />
+ <dllmap wordsize="64" dll="libc" target="/system/lib64/libc.so" />
+ <dllmap wordsize="32" dll="intl" target="/system/lib/libc.so" />
+ <dllmap wordsize="64" dll="intl" target="/system/lib64/libc.so" />
+ <dllmap wordsize="32" dll="libintl" target="/system/lib/libc.so" />
+ <dllmap wordsize="64" dll="libintl" target="/system/lib64/libc.so" />
+ <dllmap dll="MonoPosixHelper" target="libMonoPosixHelper.so" />
+ <dllmap dll="System.Native" target="libmono-native.so" />
+ <dllmap wordsize="32" dll="i:msvcrt" target="/system/lib/libc.so" />
+ <dllmap wordsize="64" dll="i:msvcrt" target="/system/lib64/libc.so" />
+ <dllmap wordsize="32" dll="i:msvcrt.dll" target="/system/lib/libc.so" />
+ <dllmap wordsize="64" dll="i:msvcrt.dll" target="/system/lib64/libc.so" />
+ <dllmap wordsize="32" dll="sqlite" target="/system/lib/libsqlite.so" />
+ <dllmap wordsize="64" dll="sqlite" target="/system/lib64/libsqlite.so" />
+ <dllmap wordsize="32" dll="sqlite3" target="/system/lib/libsqlite.so" />
+ <dllmap wordsize="64" dll="sqlite3" target="/system/lib64/libsqlite.so" />
+ <dllmap wordsize="32" dll="liblog" target="/system/lib/liblog.so" />
+ <dllmap wordsize="64" dll="liblog" target="/system/lib64/liblog.so" />
+ <dllmap dll="i:kernel32.dll">
+ <dllentry dll="__Internal" name="CopyMemory" target="mono_win32_compat_CopyMemory"/>
+ <dllentry dll="__Internal" name="FillMemory" target="mono_win32_compat_FillMemory"/>
+ <dllentry dll="__Internal" name="MoveMemory" target="mono_win32_compat_MoveMemory"/>
+ <dllentry dll="__Internal" name="ZeroMemory" target="mono_win32_compat_ZeroMemory"/>
+ </dllmap>
+</configuration>
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index c09690ba6d..89d56def7d 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -206,6 +206,8 @@ def configure(env, env_mono):
env_mono.Append(CPPDEFINES=['_REENTRANT'])
if mono_static:
+ env.Append(LINKFLAGS=['-rdynamic'])
+
mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a')
if is_apple:
@@ -281,8 +283,6 @@ def configure(env, env_mono):
libs_output_dir = get_android_out_dir(env) if is_android else '#bin'
copy_file(mono_lib_path, libs_output_dir, 'lib' + mono_so_name + sharedlib_ext)
- env.Append(LINKFLAGS='-rdynamic')
-
if not tools_enabled:
if is_desktop(env['platform']):
if not mono_root:
@@ -292,7 +292,8 @@ def configure(env, env_mono):
elif is_android:
# Compress Android Mono Config
from . import make_android_mono_config
- config_file_path = os.path.join(mono_root, 'etc', 'mono', 'config')
+ module_dir = os.getcwd()
+ config_file_path = os.path.join(module_dir, 'build_scripts', 'mono_android_config.xml')
make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/')
# Copy the required shared libraries
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 52a07037df..c7e8ea511a 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -125,12 +125,20 @@ namespace GodotTools.Export
dependencies[projectDllName] = projectDllSrcPath;
+ if (platform == OS.Platforms.Android)
{
- string platformBclDir = DeterminePlatformBclDir(platform);
+ string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext");
+ string monoAndroidAssemblyPath = Path.Combine(godotAndroidExtProfileDir, "Mono.Android.dll");
+
+ if (!File.Exists(monoAndroidAssemblyPath))
+ throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath);
- internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, platformBclDir, dependencies);
+ dependencies["Mono.Android"] = monoAndroidAssemblyPath;
}
+ var initialDependencies = dependencies.Duplicate();
+ internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies);
+
string outputDataDir = null;
if (PlatformHasTemplateDir(platform))
@@ -581,6 +589,12 @@ namespace GodotTools.Export
return null;
}
+ private static string GetBclProfileDir(string profile)
+ {
+ string templatesDir = Internal.FullTemplatesDir;
+ return Path.Combine(templatesDir, "bcl", profile);
+ }
+
private static string DeterminePlatformBclDir(string platform)
{
string templatesDir = Internal.FullTemplatesDir;
@@ -592,18 +606,45 @@ namespace GodotTools.Export
platformBclDir = Path.Combine(templatesDir, "bcl", profile);
if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll")))
+ {
+ if (PlatformRequiresCustomBcl(platform))
+ throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}");
+
platformBclDir = null; // Use the one we're running on
+ }
}
return platformBclDir;
}
+ /// <summary>
+ /// Determines whether the BCL bundled with the Godot editor can be used for the target platform,
+ /// or if it requires a custom BCL that must be distributed with the export templates.
+ /// </summary>
+ private static bool PlatformRequiresCustomBcl(string platform)
+ {
+ if (new[] {OS.Platforms.Android, OS.Platforms.HTML5}.Contains(platform))
+ return true;
+
+ // The 'net_4_x' BCL is not compatible between Windows and the other platforms.
+ // We use the names 'net_4_x_win' and 'net_4_x' to differentiate between the two.
+
+ bool isWinOrUwp = new[]
+ {
+ OS.Platforms.Windows,
+ OS.Platforms.UWP
+ }.Contains(platform);
+
+ return OS.IsWindows ? !isWinOrUwp : isWinOrUwp;
+ }
+
private static string DeterminePlatformBclProfile(string platform)
{
switch (platform)
{
case OS.Platforms.Windows:
case OS.Platforms.UWP:
+ return "net_4_x_win";
case OS.Platforms.OSX:
case OS.Platforms.X11:
case OS.Platforms.Server:
@@ -629,7 +670,7 @@ namespace GodotTools.Export
}
[MethodImpl(MethodImplOptions.InternalCall)]
- private static extern void internal_GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath,
+ private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialDependencies,
string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencies);
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index 1a8c26acd7..e89ea0c476 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -65,7 +65,7 @@ namespace GodotTools.Utils
private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android));
private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5));
- public static bool IsWindows => _isWindows.Value;
+ public static bool IsWindows => _isWindows.Value || IsUWP;
public static bool IsOSX => _isOSX.Value;
public static bool IsX11 => _isX11.Value;
public static bool IsServer => _isServer.Value;
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 4055ec005a..443b4ba841 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -219,15 +219,14 @@ int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObje
return err;
}
-uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path,
+uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_dependencies,
MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_dependencies) {
- String project_dll_name = GDMonoMarshal::mono_string_to_godot(p_project_dll_name);
- String project_dll_src_path = GDMonoMarshal::mono_string_to_godot(p_project_dll_src_path);
+ Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_dependencies);
String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config);
String custom_bcl_dir = GDMonoMarshal::mono_string_to_godot(p_custom_bcl_dir);
Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies);
- return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_bcl_dir, dependencies);
+ return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, dependencies);
}
MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) {
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index 9ae9399e1d..e02bd3be58 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -101,23 +101,32 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String>
return OK;
}
-Error get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) {
+Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies,
+ const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport");
ERR_FAIL_NULL_V(export_domain, FAILED);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
_GDMONO_SCOPE_DOMAIN_(export_domain);
- GDMonoAssembly *scripts_assembly = NULL;
- bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name,
- p_project_dll_src_path, &scripts_assembly, /* refonly: */ true);
-
- ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + p_project_dll_name + "'.");
-
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
- return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies);
+ for (const Variant *key = p_initial_dependencies.next(); key; key = p_initial_dependencies.next(key)) {
+ String assembly_name = *key;
+ String assembly_path = p_initial_dependencies[*key];
+
+ GDMonoAssembly *assembly = NULL;
+ bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true);
+
+ ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'.");
+
+ Error err = get_assembly_dependencies(assembly, search_dirs, r_dependencies);
+ if (err != OK)
+ return err;
+ }
+
+ return OK;
}
} // namespace GodotSharpExport
diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h
index 58e46e2f2d..8eb5a4d9dc 100644
--- a/modules/mono/editor/godotsharp_export.h
+++ b/modules/mono/editor/godotsharp_export.h
@@ -41,9 +41,8 @@ namespace GodotSharpExport {
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
-Error get_exported_assembly_dependencies(const String &p_project_dll_name,
- const String &p_project_dll_src_path, const String &p_build_config,
- const String &p_custom_lib_dir, Dictionary &r_dependencies);
+Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies,
+ const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies);
} // namespace GodotSharpExport
diff --git a/modules/mono/glue/Managed/Files/Array.cs b/modules/mono/glue/Managed/Files/Array.cs
index 0e7b0362e0..aba1065498 100644
--- a/modules/mono/glue/Managed/Files/Array.cs
+++ b/modules/mono/glue/Managed/Files/Array.cs
@@ -64,6 +64,11 @@ namespace Godot.Collections
return safeHandle.DangerousGetHandle();
}
+ public Array Duplicate(bool deep = false)
+ {
+ return new Array(godot_icall_Array_Duplicate(GetPtr(), deep));
+ }
+
public Error Resize(int newSize)
{
return godot_icall_Array_Resize(GetPtr(), newSize);
@@ -179,6 +184,9 @@ namespace Godot.Collections
internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex);
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Array_Duplicate(IntPtr ptr, bool deep);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static int godot_icall_Array_IndexOf(IntPtr ptr, object item);
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -250,6 +258,11 @@ namespace Godot.Collections
return from.objectArray;
}
+ public Array<T> Duplicate(bool deep = false)
+ {
+ return new Array<T>(objectArray.Duplicate(deep));
+ }
+
public Error Resize(int newSize)
{
return objectArray.Resize(newSize);
diff --git a/modules/mono/glue/Managed/Files/Dictionary.cs b/modules/mono/glue/Managed/Files/Dictionary.cs
index 6ab8549a01..d72109de92 100644
--- a/modules/mono/glue/Managed/Files/Dictionary.cs
+++ b/modules/mono/glue/Managed/Files/Dictionary.cs
@@ -80,6 +80,11 @@ namespace Godot.Collections
disposed = true;
}
+ public Dictionary Duplicate(bool deep = false)
+ {
+ return new Dictionary(godot_icall_Dictionary_Duplicate(GetPtr(), deep));
+ }
+
// IDictionary
public ICollection Keys
@@ -235,6 +240,9 @@ namespace Godot.Collections
internal extern static bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key);
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Dictionary_Duplicate(IntPtr ptr, bool deep);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key);
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -313,6 +321,11 @@ namespace Godot.Collections
return objectDict.GetPtr();
}
+ public Dictionary<TKey, TValue> Duplicate(bool deep = false)
+ {
+ return new Dictionary<TKey, TValue>(objectDict.Duplicate(deep));
+ }
+
// IDictionary<TKey, TValue>
public TValue this[TKey key]
diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp
index bfb7b0f775..e84a5becd6 100644
--- a/modules/mono/glue/collections_glue.cpp
+++ b/modules/mono/glue/collections_glue.cpp
@@ -103,6 +103,10 @@ void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) {
}
}
+Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep) {
+ return memnew(Array(ptr->duplicate(deep)));
+}
+
int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) {
return ptr->find(GDMonoMarshal::mono_object_to_variant(item));
}
@@ -224,6 +228,10 @@ MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key)
return ptr->has(GDMonoMarshal::mono_object_to_variant(key));
}
+Dictionary *godot_icall_Dictionary_Duplicate(Dictionary *ptr, MonoBoolean deep) {
+ return memnew(Dictionary(ptr->duplicate(deep)));
+}
+
MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) {
return ptr->erase(GDMonoMarshal::mono_object_to_variant(key));
}
@@ -284,6 +292,7 @@ void godot_register_collections_icalls() {
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo);
+ mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", (void *)godot_icall_Array_Duplicate);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", (void *)godot_icall_Array_IndexOf);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", (void *)godot_icall_Array_Insert);
mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", (void *)godot_icall_Array_Remove);
@@ -304,6 +313,7 @@ void godot_register_collections_icalls() {
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", (void *)godot_icall_Dictionary_Clear);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", (void *)godot_icall_Dictionary_Contains);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", (void *)godot_icall_Dictionary_ContainsKey);
+ mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Duplicate", (void *)godot_icall_Dictionary_Duplicate);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", (void *)godot_icall_Dictionary_RemoveKey);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", (void *)godot_icall_Dictionary_Remove);
mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", (void *)godot_icall_Dictionary_TryGetValue);
diff --git a/modules/mono/glue/collections_glue.h b/modules/mono/glue/collections_glue.h
index 54ed42c3b6..a79a651fcd 100644
--- a/modules/mono/glue/collections_glue.h
+++ b/modules/mono/glue/collections_glue.h
@@ -59,6 +59,8 @@ MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item);
void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index);
+Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep);
+
int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item);
void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item);
@@ -99,6 +101,8 @@ MonoBoolean godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, Mo
MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key);
+Dictionary *godot_icall_Dictionary_Duplicate(Dictionary *ptr, MonoBoolean deep);
+
MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key);
MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value);
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index a43311d281..33ba877352 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -330,7 +330,7 @@ void GDMono::initialize() {
#endif
#if defined(ANDROID_ENABLED)
- GDMonoAndroid::register_android_dl_fallback();
+ GDMonoAndroid::initialize();
#endif
GDMonoAssembly::initialize();
@@ -363,6 +363,9 @@ void GDMono::initialize() {
}
#endif
+ // NOTE: Internal calls must be registered after the Mono runtime initialization.
+ // Otherwise registration fails with the error: 'assertion 'hash != NULL' failed'.
+
root_domain = gd_initialize_mono_runtime();
ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime.");
@@ -376,6 +379,10 @@ void GDMono::initialize() {
print_verbose("Mono: Runtime initialized");
+#if defined(ANDROID_ENABLED)
+ GDMonoAndroid::register_internal_calls();
+#endif
+
// mscorlib assembly MUST be present at initialization
bool corlib_loaded = _load_corlib_assembly();
ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib assembly.");
@@ -1253,6 +1260,10 @@ GDMono::~GDMono() {
mono_jit_cleanup(root_domain);
+#if defined(ANDROID_ENABLED)
+ GDMonoAndroid::cleanup();
+#endif
+
print_verbose("Mono: Finalized");
runtime_initialized = false;
diff --git a/modules/mono/mono_gd/gd_mono_android.cpp b/modules/mono/mono_gd/gd_mono_android.cpp
index 1ee035589d..86af8d1812 100644
--- a/modules/mono/mono_gd/gd_mono_android.cpp
+++ b/modules/mono/mono_gd/gd_mono_android.cpp
@@ -34,38 +34,97 @@
#include <dlfcn.h> // dlopen, dlsym
#include <mono/utils/mono-dl-fallback.h>
+#include <sys/system_properties.h>
+#include <cstddef>
+
+#if __ANDROID_API__ < 24
+#include "thirdparty/misc/ifaddrs-android.h"
+#else
+#include <ifaddrs.h>
+#endif
#include "core/os/os.h"
#include "core/ustring.h"
+#include "platform/android/java_godot_wrapper.h"
+#include "platform/android/os_android.h"
#include "platform/android/thread_jandroid.h"
#include "../utils/path_utils.h"
#include "../utils/string_utils.h"
+#include "gd_mono_cache.h"
+#include "gd_mono_marshal.h"
+
+// Warning: JNI boilerplate ahead... continue at your own risk
namespace GDMonoAndroid {
+template <typename T>
+struct ScopedLocalRef {
+ JNIEnv *env;
+ T local_ref;
+
+ _FORCE_INLINE_ T get() const { return local_ref; }
+ _FORCE_INLINE_ operator T() const { return local_ref; }
+ _FORCE_INLINE_ operator jvalue() const { return (jvalue)local_ref; }
+
+ _FORCE_INLINE_ operator bool() const { return local_ref != NULL; }
+
+ _FORCE_INLINE_ bool operator==(std::nullptr_t) const {
+ return local_ref == nullptr;
+ }
+
+ _FORCE_INLINE_ bool operator!=(std::nullptr_t) const {
+ return local_ref != nullptr;
+ }
+
+ ScopedLocalRef(const ScopedLocalRef &) = delete;
+ ScopedLocalRef &operator=(const ScopedLocalRef &) = delete;
+
+ ScopedLocalRef(JNIEnv *p_env, T p_local_ref) :
+ env(p_env), local_ref(p_local_ref) {
+ }
+
+ ~ScopedLocalRef() {
+ if (local_ref) {
+ env->DeleteLocalRef(local_ref);
+ }
+ }
+};
+
+bool jni_exception_check(JNIEnv *p_env) {
+ if (p_env->ExceptionCheck()) {
+ // Print the exception to logcat
+ p_env->ExceptionDescribe();
+
+ p_env->ExceptionClear();
+ return true;
+ }
+
+ return false;
+}
+
String app_native_lib_dir_cache;
String determine_app_native_lib_dir() {
JNIEnv *env = ThreadAndroid::get_env();
- jclass activityThreadClass = env->FindClass("android/app/ActivityThread");
+ ScopedLocalRef<jclass> activityThreadClass(env, env->FindClass("android/app/ActivityThread"));
jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;");
- jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread);
+ ScopedLocalRef<jobject> activityThread(env, env->CallStaticObjectMethod(activityThreadClass, currentActivityThread));
jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;");
- jobject ctx = env->CallObjectMethod(activityThread, getApplication);
+ ScopedLocalRef<jobject> ctx(env, env->CallObjectMethod(activityThread, getApplication));
jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
- jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo);
+ ScopedLocalRef<jobject> applicationInfo(env, env->CallObjectMethod(ctx, getApplicationInfo));
jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;");
- jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField);
+ ScopedLocalRef<jstring> nativeLibraryDir(env, (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField));
String result;
- const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL);
- if (nativeLibraryDir_utf8) {
- result.parse_utf8(nativeLibraryDir_utf8);
- env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8);
+ const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, NULL);
+ if (nativeLibraryDirUtf8) {
+ result.parse_utf8(nativeLibraryDirUtf8);
+ env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDirUtf8);
}
return result;
@@ -90,7 +149,53 @@ int gd_mono_convert_dl_flags(int flags) {
return lflags;
}
+#ifndef GD_MONO_ANDROID_SO_NAME
+#define GD_MONO_ANDROID_SO_NAME "libmonosgen-2.0.so"
+#endif
+
+const char *mono_so_name = GD_MONO_ANDROID_SO_NAME;
+const char *godot_so_name = "libgodot_android.so";
+
+void *mono_dl_handle = NULL;
+void *godot_dl_handle = NULL;
+
+void *try_dlopen(const String &p_so_path, int p_flags) {
+ if (!FileAccess::exists(p_so_path)) {
+ if (OS::get_singleton()->is_stdout_verbose())
+ OS::get_singleton()->print("Cannot find shared library: '%s'\n", p_so_path.utf8().get_data());
+ return NULL;
+ }
+
+ int lflags = gd_mono_convert_dl_flags(p_flags);
+
+ void *handle = dlopen(p_so_path.utf8().get_data(), lflags);
+
+ if (!handle) {
+ if (OS::get_singleton()->is_stdout_verbose())
+ OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", p_so_path.utf8().get_data(), dlerror());
+ return NULL;
+ }
+
+ if (OS::get_singleton()->is_stdout_verbose())
+ OS::get_singleton()->print("Successfully loaded shared library: '%s'\n", p_so_path.utf8().get_data());
+
+ return handle;
+}
+
void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) {
+ if (p_name == NULL) {
+ // __Internal
+
+ if (!mono_dl_handle) {
+ String app_native_lib_dir = get_app_native_lib_dir();
+ String so_path = path::join(app_native_lib_dir, mono_so_name);
+
+ mono_dl_handle = try_dlopen(so_path, p_flags);
+ }
+
+ return mono_dl_handle;
+ }
+
String name = String::utf8(p_name);
if (name.ends_with(".dll.so") || name.ends_with(".exe.so")) {
@@ -100,26 +205,7 @@ void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void
String so_name = "lib-aot-" + orig_so_name;
String so_path = path::join(app_native_lib_dir, so_name);
- if (!FileAccess::exists(so_path)) {
- if (OS::get_singleton()->is_stdout_verbose())
- OS::get_singleton()->print("Cannot find shared library: '%s'\n", so_path.utf8().get_data());
- return NULL;
- }
-
- int lflags = gd_mono_convert_dl_flags(p_flags);
-
- void *handle = dlopen(so_path.utf8().get_data(), lflags);
-
- if (!handle) {
- if (OS::get_singleton()->is_stdout_verbose())
- OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", so_path.utf8().get_data(), dlerror());
- return NULL;
- }
-
- if (OS::get_singleton()->is_stdout_verbose())
- OS::get_singleton()->print("Successfully loaded AOT shared library: '%s'\n", so_path.utf8().get_data());
-
- return handle;
+ return try_dlopen(so_path, p_flags);
}
return NULL;
@@ -131,16 +217,469 @@ void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, vo
if (sym_addr)
return sym_addr;
+ if (p_handle == mono_dl_handle && godot_dl_handle) {
+ // Looking up for '__Internal' P/Invoke. We want to search in both the Mono and Godot shared libraries.
+ // This is needed to resolve the monodroid P/Invoke functions that are defined at the bottom of the file.
+ sym_addr = dlsym(godot_dl_handle, p_name);
+
+ if (sym_addr)
+ return sym_addr;
+ }
+
if (r_err)
*r_err = str_format_new("%s\n", dlerror());
return NULL;
}
-void register_android_dl_fallback() {
- mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, NULL, NULL);
+void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) {
+ dlclose(p_handle);
+
+ // Not sure if this ever happens. Does Mono close the handle for the main module?
+ if (p_handle == mono_dl_handle)
+ mono_dl_handle = NULL;
+
+ return NULL;
+}
+
+int32_t build_version_sdk_int = 0;
+
+int32_t get_build_version_sdk_int() {
+ // The JNI code is the equivalent of:
+ //
+ // android.os.Build.VERSION.SDK_INT
+
+ if (build_version_sdk_int == 0) {
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ jclass versionClass = env->FindClass("android/os/Build$VERSION");
+ ERR_FAIL_NULL_V(versionClass, 0);
+
+ jfieldID sdkIntField = env->GetStaticFieldID(versionClass, "SDK_INT", "I");
+ ERR_FAIL_NULL_V(sdkIntField, 0);
+
+ build_version_sdk_int = (int32_t)env->GetStaticIntField(versionClass, sdkIntField);
+ }
+
+ return build_version_sdk_int;
+}
+
+jobject certStore = NULL; // KeyStore
+
+MonoBoolean _gd_mono_init_cert_store() {
+ // The JNI code is the equivalent of:
+ //
+ // try {
+ // certStoreLocal = KeyStore.getInstance("AndroidCAStore");
+ // certStoreLocal.load(null);
+ // certStore = certStoreLocal;
+ // return true;
+ // } catch (Exception e) {
+ // return false;
+ // }
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore"));
+
+ jmethodID getInstance = env->GetStaticMethodID(keyStoreClass, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyStore;");
+ jmethodID load = env->GetMethodID(keyStoreClass, "load", "(Ljava/security/KeyStore$LoadStoreParameter;)V");
+
+ ScopedLocalRef<jstring> androidCAStoreString(env, env->NewStringUTF("AndroidCAStore"));
+
+ ScopedLocalRef<jobject> certStoreLocal(env, env->CallStaticObjectMethod(keyStoreClass, getInstance, androidCAStoreString.get()));
+
+ if (jni_exception_check(env))
+ return 0;
+
+ env->CallVoidMethod(certStoreLocal, load, NULL);
+
+ if (jni_exception_check(env))
+ return 0;
+
+ certStore = env->NewGlobalRef(certStoreLocal);
+
+ return 1;
+}
+
+MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) {
+ // The JNI code is the equivalent of:
+ //
+ // Certificate certificate = certStore.getCertificate(alias);
+ // if (certificate == null)
+ // return null;
+ // return certificate.getEncoded();
+
+ MonoError mono_error;
+ char *alias_utf8 = mono_string_to_utf8_checked(p_alias, &mono_error);
+
+ if (!mono_error_ok(&mono_error)) {
+ ERR_PRINTS(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&mono_error) + "'.");
+ mono_error_cleanup(&mono_error);
+ return NULL;
+ }
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ ScopedLocalRef<jstring> js_alias(env, env->NewStringUTF(alias_utf8));
+ mono_free(alias_utf8);
+
+ ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore"));
+ ERR_FAIL_NULL_V(keyStoreClass, NULL);
+ ScopedLocalRef<jclass> certificateClass(env, env->FindClass("java/security/cert/Certificate"));
+ ERR_FAIL_NULL_V(certificateClass, NULL);
+
+ jmethodID getCertificate = env->GetMethodID(keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;");
+ ERR_FAIL_NULL_V(getCertificate, NULL);
+
+ jmethodID getEncoded = env->GetMethodID(certificateClass, "getEncoded", "()[B");
+ ERR_FAIL_NULL_V(getEncoded, NULL);
+
+ ScopedLocalRef<jobject> certificate(env, env->CallObjectMethod(certStore, getCertificate, js_alias.get()));
+
+ if (!certificate)
+ return NULL;
+
+ ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded));
+ jsize encodedLength = env->GetArrayLength(encoded);
+
+ MonoArray *encoded_ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), encodedLength);
+ uint8_t *dest = (uint8_t *)mono_array_addr(encoded_ret, uint8_t, 0);
+
+ env->GetByteArrayRegion(encoded, 0, encodedLength, reinterpret_cast<jbyte *>(dest));
+
+ return encoded_ret;
+}
+
+void initialize() {
+ // We need to set this environment variable to make the monodroid BCL use btls instead of legacy as the default provider
+ OS::get_singleton()->set_environment("XA_TLS_PROVIDER", "btls");
+
+ mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, NULL);
+
+ String app_native_lib_dir = get_app_native_lib_dir();
+ String so_path = path::join(app_native_lib_dir, godot_so_name);
+
+ godot_dl_handle = try_dlopen(so_path, gd_mono_convert_dl_flags(MONO_DL_LAZY));
+}
+
+void register_internal_calls() {
+ mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store);
+ mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup);
+}
+
+void cleanup() {
+ // This is called after shutting down the Mono runtime
+
+ if (mono_dl_handle)
+ gd_mono_android_dlclose(mono_dl_handle, NULL);
+
+ if (godot_dl_handle)
+ gd_mono_android_dlclose(godot_dl_handle, NULL);
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ if (certStore) {
+ env->DeleteGlobalRef(certStore);
+ certStore = NULL;
+ }
}
} // namespace GDMonoAndroid
+using namespace GDMonoAndroid;
+
+// The following are P/Invoke functions required by the monodroid profile of the BCL.
+// These are P/Invoke functions and not internal calls, hence why they use
+// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'.
+
+#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default")))
+
+GD_PINVOKE_EXPORT int32_t _monodroid_get_android_api_level() {
+ return get_build_version_sdk_int();
+}
+
+GD_PINVOKE_EXPORT void monodroid_free(void *ptr) {
+ free(ptr);
+}
+
+GD_PINVOKE_EXPORT int32_t monodroid_get_system_property(const char *p_name, char **r_value) {
+ char prop_value_str[PROP_VALUE_MAX + 1] = { 0 };
+
+ int len = __system_property_get(p_name, prop_value_str);
+
+ if (r_value) {
+ if (len >= 0) {
+ *r_value = (char *)malloc(len + 1);
+ if (!*r_value)
+ return -1;
+ memcpy(*r_value, prop_value_str, len);
+ (*r_value)[len] = '\0';
+ } else {
+ *r_value = NULL;
+ }
+ }
+
+ return len;
+}
+
+GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_up_state(const char *p_ifname, mono_bool *r_is_up) {
+ // The JNI code is the equivalent of:
+ //
+ // NetworkInterface.getByName(p_ifname).isUp()
+
+ if (!r_is_up || !p_ifname || strlen(p_ifname) == 0)
+ return 0;
+
+ *r_is_up = 0;
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface");
+ ERR_FAIL_NULL_V(networkInterfaceClass, 0);
+
+ jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;");
+ ERR_FAIL_NULL_V(getByName, 0);
+
+ jmethodID isUp = env->GetMethodID(networkInterfaceClass, "isUp", "()Z");
+ ERR_FAIL_NULL_V(isUp, 0);
+
+ ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname));
+ ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get()));
+
+ if (!networkInterface)
+ return 0;
+
+ *r_is_up = (mono_bool)env->CallBooleanMethod(networkInterface, isUp);
+
+ return 1;
+}
+
+GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_supports_multicast(const char *p_ifname, mono_bool *r_supports_multicast) {
+ // The JNI code is the equivalent of:
+ //
+ // NetworkInterface.getByName(p_ifname).supportsMulticast()
+
+ if (!r_supports_multicast || !p_ifname || strlen(p_ifname) == 0)
+ return 0;
+
+ *r_supports_multicast = 0;
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface");
+ ERR_FAIL_NULL_V(networkInterfaceClass, 0);
+
+ jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;");
+ ERR_FAIL_NULL_V(getByName, 0);
+
+ jmethodID supportsMulticast = env->GetMethodID(networkInterfaceClass, "supportsMulticast", "()Z");
+ ERR_FAIL_NULL_V(supportsMulticast, 0);
+
+ ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname));
+ ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get()));
+
+ if (!networkInterface)
+ return 0;
+
+ *r_supports_multicast = (mono_bool)env->CallBooleanMethod(networkInterface, supportsMulticast);
+
+ return 1;
+}
+
+static const int dns_servers_len = 8;
+
+static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dns_servers_count) {
+ // The JNI code is the equivalent of:
+ //
+ // ConnectivityManager connectivityManager = (ConnectivityManager)getApplicationContext()
+ // .getSystemService(Context.CONNECTIVITY_SERVICE);
+ // Network activeNerwork = connectivityManager.getActiveNetwork();
+ // LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNerwork);
+ // List<String> dnsServers = linkProperties.getDnsServers().stream()
+ // .map(inetAddress -> inetAddress.getHostAddress()).collect(Collectors.toList());
+
+#ifdef DEBUG_ENABLED
+ CRASH_COND(get_build_version_sdk_int() < 23);
+#endif
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ GodotJavaWrapper *godot_java = ((OS_Android *)OS::get_singleton())->get_godot_java();
+ jobject activity = godot_java->get_activity();
+
+ ScopedLocalRef<jclass> activityClass(env, env->GetObjectClass(activity));
+ ERR_FAIL_NULL(activityClass);
+
+ jmethodID getApplicationContext = env->GetMethodID(activityClass, "getApplicationContext", "()Landroid/content/Context;");
+
+ ScopedLocalRef<jobject> applicationContext(env, env->CallObjectMethod(activity, getApplicationContext));
+
+ ScopedLocalRef<jclass> contextClass(env, env->FindClass("android/content/Context"));
+ ERR_FAIL_NULL(contextClass);
+
+ jfieldID connectivityServiceField = env->GetStaticFieldID(contextClass, "CONNECTIVITY_SERVICE", "Ljava/lang/String;");
+ ScopedLocalRef<jstring> connectivityServiceString(env, (jstring)env->GetStaticObjectField(contextClass, connectivityServiceField));
+
+ jmethodID getSystemService = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
+
+ ScopedLocalRef<jobject> connectivityManager(env, env->CallObjectMethod(applicationContext, getSystemService, connectivityServiceString.get()));
+
+ if (!connectivityManager)
+ return;
+
+ ScopedLocalRef<jclass> connectivityManagerClass(env, env->FindClass("android/net/ConnectivityManager"));
+ ERR_FAIL_NULL(connectivityManagerClass);
+
+ jmethodID getActiveNetwork = env->GetMethodID(connectivityManagerClass, "getActiveNetwork", "()Landroid/net/Network;");
+ ERR_FAIL_NULL(getActiveNetwork);
+
+ ScopedLocalRef<jobject> activeNetwork(env, env->CallObjectMethod(connectivityManager, getActiveNetwork));
+
+ if (!activeNetwork)
+ return;
+
+ jmethodID getLinkProperties = env->GetMethodID(connectivityManagerClass,
+ "getLinkProperties", "(Landroid/net/Network;)Landroid/net/LinkProperties;");
+ ERR_FAIL_NULL(getLinkProperties);
+
+ ScopedLocalRef<jobject> linkProperties(env, env->CallObjectMethod(connectivityManager, getLinkProperties, activeNetwork.get()));
+
+ if (!linkProperties)
+ return;
+
+ ScopedLocalRef<jclass> linkPropertiesClass(env, env->FindClass("android/net/LinkProperties"));
+ ERR_FAIL_NULL(linkPropertiesClass);
+
+ jmethodID getDnsServers = env->GetMethodID(linkPropertiesClass, "getDnsServers", "()Ljava/util/List;");
+ ERR_FAIL_NULL(getDnsServers);
+
+ ScopedLocalRef<jobject> dnsServers(env, env->CallObjectMethod(linkProperties, getDnsServers));
+
+ if (!dnsServers)
+ return;
+
+ ScopedLocalRef<jclass> listClass(env, env->FindClass("java/util/List"));
+ ERR_FAIL_NULL(listClass);
+
+ jmethodID listSize = env->GetMethodID(listClass, "size", "()I");
+ ERR_FAIL_NULL(listSize);
+
+ int dnsServersCount = env->CallIntMethod(dnsServers, listSize);
+
+ if (dnsServersCount > dns_servers_len)
+ dnsServersCount = dns_servers_len;
+
+ if (dnsServersCount <= 0)
+ return;
+
+ jmethodID listGet = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
+ ERR_FAIL_NULL(listGet);
+
+ ScopedLocalRef<jclass> inetAddressClass(env, env->FindClass("java/net/InetAddress"));
+ ERR_FAIL_NULL(inetAddressClass);
+
+ jmethodID getHostAddress = env->GetMethodID(inetAddressClass, "getHostAddress", "()Ljava/lang/String;");
+ ERR_FAIL_NULL(getHostAddress);
+
+ for (int i = 0; i < dnsServersCount; i++) {
+ ScopedLocalRef<jobject> dnsServer(env, env->CallObjectMethod(dnsServers, listGet, (jint)i));
+ if (!dnsServer)
+ continue;
+
+ ScopedLocalRef<jstring> hostAddress(env, (jstring)env->CallObjectMethod(dnsServer, getHostAddress));
+ const char *host_address = env->GetStringUTFChars(hostAddress, 0);
+
+ r_dns_servers[i] = strdup(host_address); // freed by the BCL
+ (*dns_servers_count)++;
+
+ env->ReleaseStringUTFChars(hostAddress, host_address);
+ }
+
+ // jesus...
+}
+
+GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array) {
+ if (!r_dns_servers_array)
+ return -1;
+
+ *r_dns_servers_array = NULL;
+
+ char *dns_servers[dns_servers_len];
+ int dns_servers_count = 0;
+
+ if (_monodroid_get_android_api_level() < 26) {
+ // The 'net.dns*' system properties are no longer available in Android 8.0 (API level 26) and greater:
+ // https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri
+
+ char prop_name[] = "net.dns*";
+
+ for (int i = 0; i < dns_servers_len; i++) {
+ prop_name[7] = (char)(i + 0x31);
+ char *prop_value;
+ int32_t len = monodroid_get_system_property(prop_name, &prop_value);
+
+ if (len > 0) {
+ dns_servers[dns_servers_count] = strndup(prop_value, (size_t)len); // freed by the BCL
+ dns_servers_count++;
+ free(prop_value);
+ }
+ }
+ } else {
+ // Alternative for Oreo and greater
+ interop_get_active_network_dns_servers(dns_servers, &dns_servers_count);
+ }
+
+ if (dns_servers_count > 0) {
+ size_t ret_size = sizeof(char *) * (size_t)dns_servers_count;
+ *r_dns_servers_array = malloc(ret_size); // freed by the BCL
+ memcpy(*r_dns_servers_array, dns_servers, ret_size);
+ }
+
+ return dns_servers_count;
+}
+
+GD_PINVOKE_EXPORT const char *_monodroid_timezone_get_default_id() {
+ // The JNI code is the equivalent of:
+ //
+ // TimeZone.getDefault().getID()
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ ScopedLocalRef<jclass> timeZoneClass(env, env->FindClass("java/util/TimeZone"));
+ ERR_FAIL_NULL_V(timeZoneClass, NULL);
+
+ jmethodID getDefault = env->GetStaticMethodID(timeZoneClass, "getDefault", "()Ljava/util/TimeZone;");
+ ERR_FAIL_NULL_V(getDefault, NULL);
+
+ jmethodID getID = env->GetMethodID(timeZoneClass, "getID", "()Ljava/lang/String;");
+ ERR_FAIL_NULL_V(getID, NULL);
+
+ ScopedLocalRef<jobject> defaultTimeZone(env, env->CallStaticObjectMethod(timeZoneClass, getDefault));
+
+ if (!defaultTimeZone)
+ return NULL;
+
+ ScopedLocalRef<jstring> defaultTimeZoneID(env, (jstring)env->CallObjectMethod(defaultTimeZone, getID));
+
+ if (!defaultTimeZoneID)
+ return NULL;
+
+ const char *default_time_zone_id = env->GetStringUTFChars(defaultTimeZoneID, 0);
+
+ char *result = strdup(default_time_zone_id); // freed by the BCL
+
+ env->ReleaseStringUTFChars(defaultTimeZoneID, default_time_zone_id);
+
+ return result;
+}
+
+GD_PINVOKE_EXPORT int32_t _monodroid_getifaddrs(struct ifaddrs **p_ifap) {
+ return getifaddrs(p_ifap);
+}
+
+GD_PINVOKE_EXPORT void _monodroid_freeifaddrs(struct ifaddrs *p_ifap) {
+ freeifaddrs(p_ifap);
+}
+
#endif
diff --git a/modules/mono/mono_gd/gd_mono_android.h b/modules/mono/mono_gd/gd_mono_android.h
index 72bc799bfd..20ca266428 100644
--- a/modules/mono/mono_gd/gd_mono_android.h
+++ b/modules/mono/mono_gd/gd_mono_android.h
@@ -39,7 +39,11 @@ namespace GDMonoAndroid {
String get_app_native_lib_dir();
-void register_android_dl_fallback();
+void initialize();
+
+void register_internal_calls();
+
+void cleanup();
} // namespace GDMonoAndroid
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index b21f92cdd8..a6d6da4f2b 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -42,20 +42,20 @@ struct CachedData {
// corlib classes
// Let's use the no-namespace format for these too
- GDMonoClass *class_MonoObject;
- GDMonoClass *class_bool;
- GDMonoClass *class_int8_t;
- GDMonoClass *class_int16_t;
- GDMonoClass *class_int32_t;
- GDMonoClass *class_int64_t;
- GDMonoClass *class_uint8_t;
- GDMonoClass *class_uint16_t;
- GDMonoClass *class_uint32_t;
- GDMonoClass *class_uint64_t;
- GDMonoClass *class_float;
- GDMonoClass *class_double;
- GDMonoClass *class_String;
- GDMonoClass *class_IntPtr;
+ GDMonoClass *class_MonoObject; // object
+ GDMonoClass *class_bool; // bool
+ GDMonoClass *class_int8_t; // sbyte
+ GDMonoClass *class_int16_t; // short
+ GDMonoClass *class_int32_t; // int
+ GDMonoClass *class_int64_t; // long
+ GDMonoClass *class_uint8_t; // byte
+ GDMonoClass *class_uint16_t; // ushort
+ GDMonoClass *class_uint32_t; // uint
+ GDMonoClass *class_uint64_t; // ulong
+ GDMonoClass *class_float; // float
+ GDMonoClass *class_double; // double
+ GDMonoClass *class_String; // string
+ GDMonoClass *class_IntPtr; // System.IntPtr
GDMonoClass *class_System_Collections_IEnumerable;
GDMonoClass *class_System_Collections_IDictionary;
diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp
index 3827960e6b..f74fe5715c 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.cpp
+++ b/modules/mono/mono_gd/gd_mono_marshal.cpp
@@ -935,6 +935,8 @@ Array mono_array_to_Array(MonoArray *p_array) {
return ret;
}
+// TODO: Use memcpy where possible
+
MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array) {
PoolIntArray::Read r = p_array.read();
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index b2791cfc8b..bf353d287f 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -3464,6 +3464,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
ofs = ofs.snapped(Vector2(snap, snap));
}
ofs /= EDSCALE;
+ ofs /= graph->get_zoom();
Set<int> vn;