gdharness 1.3.39 → 1.3.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -107,7 +107,45 @@ func enable_plugin(params: Dictionary) -> Dictionary:
107
107
  if err != OK:
108
108
  return _log.failure("Failed to save project.godot: " + error_string(err))
109
109
 
110
- return {"plugin_name": plugin_name, "action": "enabled", "enabled_plugins": _names_of(enabled_paths)}
110
+ var answer: Dictionary = {
111
+ "plugin_name": plugin_name, "action": "enabled", "enabled_plugins": _names_of(enabled_paths)
112
+ }
113
+ var unloadable: String = _why_not_loaded(plugin_cfg_path)
114
+ if not unloadable.is_empty():
115
+ answer["note"] = unloadable
116
+ return answer
117
+
118
+
119
+ # What the editor finds wrong with the plugin at [param plugin_cfg_path] when it loads it, the checks
120
+ # EditorNode::set_addon_plugin_enabled makes, or "" when it would load. Written all the same, since
121
+ # the script can be put right before the editor next starts.
122
+ static func _why_not_loaded(plugin_cfg_path: String) -> String:
123
+ var config: ConfigFile = ConfigFile.new()
124
+ if config.load(plugin_cfg_path) != OK:
125
+ return "The editor cannot read %s, so it will not enable the plugin." % plugin_cfg_path
126
+ if not config.has_section_key("plugin", "script"):
127
+ return "%s has no script field, so the editor will not enable the plugin." % plugin_cfg_path
128
+ var script_name: String = str(config.get_value("plugin", "script"))
129
+ if script_name.is_empty():
130
+ return ""
131
+ var script_path: String = plugin_cfg_path.get_base_dir().path_join(script_name)
132
+ if not FileAccess.file_exists(script_path):
133
+ return "%s does not exist, so the editor will not enable the plugin." % script_path
134
+ # Loaded only to learn whether the editor could; what the engine says of a script that does not
135
+ # parse is about a file the answer does not describe.
136
+ printerr(GdhLog.ASIDE)
137
+ var script: Script = ResourceLoader.load(script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE)
138
+ printerr(GdhLog.RESUMED)
139
+ if script == null or script.get_instance_base_type() == &"":
140
+ return (
141
+ "%s does not compile, so the editor disables the plugin again at its next start." % script_path
142
+ + " script_diagnostics names the error."
143
+ )
144
+ if not ClassDB.is_parent_class(script.get_instance_base_type(), "EditorPlugin"):
145
+ return "%s does not extend EditorPlugin, so the editor loads nothing for it." % script_path
146
+ if not script.is_tool():
147
+ return "%s is not a @tool script, so the editor loads nothing for it." % script_path
148
+ return ""
111
149
 
112
150
 
113
151
  # Disable a plugin
@@ -194,6 +194,12 @@ func unset_project_setting(params: Dictionary) -> Dictionary:
194
194
  answer["removed"] = false
195
195
  answer["saved"] = false
196
196
  answer["note"] = "project.godot does not set %s, so there was nothing to take out." % setting_path
197
+ var near: PackedStringArray = PackedStringArray() if held else _settings_near(setting_path)
198
+ if not near.is_empty():
199
+ answer["note"] += (
200
+ " Nor does the engine declare it; the nearest %s %s."
201
+ % ["setting is" if near.size() == 1 else "settings are", ", ".join(near)]
202
+ )
197
203
  return _with_what_holds(answer, setting_path)
198
204
  answer["old_value"] = _values.serialize_value(value)
199
205
  # The default rather than null for a setting the engine declares: null takes the declaration away
@@ -221,8 +227,12 @@ func _with_what_holds(answer: Dictionary, setting_path: String) -> Dictionary:
221
227
 
222
228
 
223
229
  # The settings in the same section as [param setting_path] named nearest it, for a new one that is
224
- # likely a misspelt one: a setting nothing declares is written all the same, and is never read.
230
+ # likely a misspelt one: a setting nothing declares is written all the same, and is never read. None
231
+ # for an override of a setting the engine has, which its feature's builds read in the base's place.
225
232
  static func _settings_near(setting_path: String) -> PackedStringArray:
233
+ var base: String = GdhSettingReads.override_base(setting_path)
234
+ if not base.is_empty() and ProjectSettings.has_setting(base):
235
+ return PackedStringArray()
226
236
  var section: String = setting_path.get_slice("/", 0) + "/"
227
237
  var names: PackedStringArray = []
228
238
  for info: Dictionary in ProjectSettings.get_property_list():
@@ -383,19 +383,16 @@ func _readable(value: Variant, relative_to: Node) -> Variant:
383
383
  return _values.serialize_value(value)
384
384
 
385
385
 
386
- ## What [param target] stores, by name, but its script and the properties in [param leave]. A list
387
- ## or a map is copied, since a setter can change the one it holds in place.
386
+ ## What [param target] stores, by name, but its script and the properties in [param leave], in the
387
+ ## names a read answers with: a Curve's points as point_0/position and the rest rather than the _data
388
+ ## list every point write changes. A list or a map is copied, since a setter can change the one it
389
+ ## holds in place.
388
390
  func _stored(target: Object, leave: Dictionary) -> Dictionary:
389
391
  var held: Dictionary = {}
390
- for entry: Dictionary in target.get_property_list():
392
+ for entry: Dictionary in GdhShapes.read_properties(target):
391
393
  var name: String = str(entry["name"])
392
394
  var usage: int = GdhRead.as_int(entry["usage"])
393
- if (
394
- usage & PROPERTY_USAGE_STORAGE == 0
395
- or usage & HEADINGS != 0
396
- or name == "script"
397
- or leave.has(name)
398
- ):
395
+ if usage & HEADINGS != 0 or name == "script" or leave.has(name):
399
396
  continue
400
397
  var value: Variant = target.get(name)
401
398
  if value is Array:
package/build/keeper.js CHANGED
@@ -7053,7 +7053,18 @@ public static class GdharnessDesktop {
7053
7053
  }
7054
7054
 
7055
7055
  [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
7056
- static extern IntPtr CreateDesktopW(string name, IntPtr device, IntPtr mode, uint flags, uint access, IntPtr attributes);
7056
+ static extern IntPtr CreateDesktopExW(string name, IntPtr device, IntPtr mode, uint flags, uint access, IntPtr attributes, uint heapKilobytes, IntPtr reserved);
7057
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
7058
+ static extern IntPtr CreateWindowStationW(string name, uint flags, uint access, IntPtr attributes);
7059
+ [DllImport("user32.dll", SetLastError = true)] static extern IntPtr GetProcessWindowStation();
7060
+ [DllImport("user32.dll", SetLastError = true)] static extern bool SetProcessWindowStation(IntPtr station);
7061
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
7062
+ static extern bool GetUserObjectInformationW(IntPtr handle, int index, StringBuilder into, int size, out int needed);
7063
+ const uint WindowStationAllAccess = 0x37F;
7064
+ const int ObjectName = 2;
7065
+ // What Windows gives an interactive desktop. A non-interactive station's desktops get a small
7066
+ // fraction of that by default, and one desktop here holds every engine a run starts.
7067
+ const uint DesktopHeapKilobytes = 20480;
7057
7068
 
7058
7069
  [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
7059
7070
  static extern bool CreateProcessW(string application, StringBuilder commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inherit, uint flags, IntPtr environment, string directory, ref StartupInfo startup, out Started started);
@@ -7144,12 +7155,34 @@ public static class GdharnessDesktop {
7144
7155
  StartupInfo startup = new StartupInfo();
7145
7156
  startup.cb = Marshal.SizeOf(typeof(StartupInfo));
7146
7157
  if (desktop.Length > 0) {
7147
- Step(stepFile, "creating the " + desktop + " desktop");
7148
- IntPtr held = CreateDesktopW(desktop, IntPtr.Zero, IntPtr.Zero, 0, GenericAll, IntPtr.Zero);
7158
+ Step(stepFile, "opening the session's own window station");
7159
+ IntPtr station = CreateWindowStationW(null, 0, WindowStationAllAccess, IntPtr.Zero);
7160
+ if (station == IntPtr.Zero) {
7161
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateWindowStationW");
7162
+ }
7163
+ StringBuilder stationName = new StringBuilder(256);
7164
+ int needed;
7165
+ if (!GetUserObjectInformationW(station, ObjectName, stationName, stationName.Capacity * 2, out needed)) {
7166
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "GetUserObjectInformationW");
7167
+ }
7168
+ // A desktop is made in the calling process's station, so this one is the caller's while it is.
7169
+ IntPtr own = GetProcessWindowStation();
7170
+ if (!SetProcessWindowStation(station)) {
7171
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "SetProcessWindowStation");
7172
+ }
7173
+ IntPtr held;
7174
+ int made;
7175
+ try {
7176
+ Step(stepFile, "creating the " + desktop + " desktop");
7177
+ held = CreateDesktopExW(desktop, IntPtr.Zero, IntPtr.Zero, 0, GenericAll, IntPtr.Zero, DesktopHeapKilobytes, IntPtr.Zero);
7178
+ made = Marshal.GetLastWin32Error();
7179
+ } finally {
7180
+ SetProcessWindowStation(own);
7181
+ }
7149
7182
  if (held == IntPtr.Zero) {
7150
- throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateDesktopW " + desktop);
7183
+ throw new Win32Exception(made, "CreateDesktopExW " + stationName + "\\\\" + desktop);
7151
7184
  }
7152
- startup.desktop = desktop;
7185
+ startup.desktop = stationName + "\\\\" + desktop;
7153
7186
  }
7154
7187
  startup.flags = UseStdHandles;
7155
7188
  if (noActivate) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.3.39",
3
+ "version": "1.3.41",
4
4
  "mcpName": "io.github.Aureliolo/gdharness",
5
5
  "description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
6
6
  "type": "module",