gdharness 1.3.39 → 1.3.40

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.
package/build/cli.js CHANGED
@@ -852,7 +852,18 @@ public static class GdharnessDesktop {
852
852
  }
853
853
 
854
854
  [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
855
- static extern IntPtr CreateDesktopW(string name, IntPtr device, IntPtr mode, uint flags, uint access, IntPtr attributes);
855
+ static extern IntPtr CreateDesktopExW(string name, IntPtr device, IntPtr mode, uint flags, uint access, IntPtr attributes, uint heapKilobytes, IntPtr reserved);
856
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
857
+ static extern IntPtr CreateWindowStationW(string name, uint flags, uint access, IntPtr attributes);
858
+ [DllImport("user32.dll", SetLastError = true)] static extern IntPtr GetProcessWindowStation();
859
+ [DllImport("user32.dll", SetLastError = true)] static extern bool SetProcessWindowStation(IntPtr station);
860
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
861
+ static extern bool GetUserObjectInformationW(IntPtr handle, int index, StringBuilder into, int size, out int needed);
862
+ const uint WindowStationAllAccess = 0x37F;
863
+ const int ObjectName = 2;
864
+ // What Windows gives an interactive desktop. A non-interactive station's desktops get a small
865
+ // fraction of that by default, and one desktop here holds every engine a run starts.
866
+ const uint DesktopHeapKilobytes = 20480;
856
867
 
857
868
  [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
858
869
  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);
@@ -943,12 +954,34 @@ public static class GdharnessDesktop {
943
954
  StartupInfo startup = new StartupInfo();
944
955
  startup.cb = Marshal.SizeOf(typeof(StartupInfo));
945
956
  if (desktop.Length > 0) {
946
- Step(stepFile, "creating the " + desktop + " desktop");
947
- IntPtr held = CreateDesktopW(desktop, IntPtr.Zero, IntPtr.Zero, 0, GenericAll, IntPtr.Zero);
957
+ Step(stepFile, "opening the session's own window station");
958
+ IntPtr station = CreateWindowStationW(null, 0, WindowStationAllAccess, IntPtr.Zero);
959
+ if (station == IntPtr.Zero) {
960
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateWindowStationW");
961
+ }
962
+ StringBuilder stationName = new StringBuilder(256);
963
+ int needed;
964
+ if (!GetUserObjectInformationW(station, ObjectName, stationName, stationName.Capacity * 2, out needed)) {
965
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "GetUserObjectInformationW");
966
+ }
967
+ // A desktop is made in the calling process's station, so this one is the caller's while it is.
968
+ IntPtr own = GetProcessWindowStation();
969
+ if (!SetProcessWindowStation(station)) {
970
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "SetProcessWindowStation");
971
+ }
972
+ IntPtr held;
973
+ int made;
974
+ try {
975
+ Step(stepFile, "creating the " + desktop + " desktop");
976
+ held = CreateDesktopExW(desktop, IntPtr.Zero, IntPtr.Zero, 0, GenericAll, IntPtr.Zero, DesktopHeapKilobytes, IntPtr.Zero);
977
+ made = Marshal.GetLastWin32Error();
978
+ } finally {
979
+ SetProcessWindowStation(own);
980
+ }
948
981
  if (held == IntPtr.Zero) {
949
- throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateDesktopW " + desktop);
982
+ throw new Win32Exception(made, "CreateDesktopExW " + stationName + "\\\\" + desktop);
950
983
  }
951
- startup.desktop = desktop;
984
+ startup.desktop = stationName + "\\\\" + desktop;
952
985
  }
953
986
  startup.flags = UseStdHandles;
954
987
  if (noActivate) {
@@ -17604,7 +17637,7 @@ var init_tool_definitions = __esm(() => {
17604
17637
  visible: {
17605
17638
  type: "boolean",
17606
17639
  ops: ["start"],
17607
- description: "start: on Windows, show the window on the desktop in use. By default a windowed run this server starts goes on a desktop of its own, where it renders and runtime_capture works but the window never shows and never takes the keyboard. A game a visible editor plays opens on the desktop in use, and one a hidden editor (editor_launch hidden: true) plays goes on its desktop. Elsewhere than Windows the window shows either way. Default false."
17640
+ description: "start: on Windows, show the window on the desktop in use. By default a windowed run this server starts goes on a desktop of its own, where it renders and runtime_capture works but the window never shows and never takes the keyboard or the pointer. A game a visible editor plays opens on the desktop in use, and one a hidden editor (editor_launch hidden: true) plays goes on its desktop. Elsewhere than Windows the window shows either way. Default false."
17608
17641
  },
17609
17642
  silent: {
17610
17643
  type: "boolean",
@@ -58235,7 +58268,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
58235
58268
  These need the runtime autoload, which \`gdharness setup\` registers. \`runtime_capture\` needs a
58236
58269
  window and refuses headless rather than handing back the last frame anything drew. On Windows a
58237
58270
  windowed run this server starts is on a desktop of its own, where it draws and can be captured but
58238
- never shows or takes the keyboard.
58271
+ never shows or takes the keyboard or the pointer.
58239
58272
 
58240
58273
  The autoload reads these settings and no others. \`${BIND_ADDRESS_SETTING}\`
58241
58274
  is \`127.0.0.1\` and should stay there: the command set includes \`call_method\`, \`set_property\`
@@ -125,15 +125,16 @@ static func no_animation(player: AnimationPlayer, called: String, animation_name
125
125
 
126
126
  static func _summary(anim: Animation) -> Dictionary:
127
127
  return {
128
- "length": anim.length,
128
+ "length": held_seconds(anim.length),
129
129
  "loopMode": LOOP_MODE_NAMES.get(anim.loop_mode, str(anim.loop_mode)),
130
- "step": anim.step,
130
+ "step": held_seconds(anim.step),
131
131
  }
132
132
 
133
133
 
134
- ## [param seconds], a key's time, as the shortest decimal holding the same 32-bit float. A scene keeps
135
- ## key times as 32-bit floats, so a key put at 0.7 read back as 0.699999988079071.
136
- static func key_time(seconds: float) -> float:
134
+ ## [param seconds], a key's time or an animation's length or step, as the shortest decimal holding
135
+ ## the same 32-bit float. An animation keeps all three as 32-bit floats, so a key put at 0.7 read
136
+ ## back as 0.699999988079071 and a step of 0.1 as 0.100000001490116.
137
+ static func held_seconds(seconds: float) -> float:
137
138
  var held: float = PackedFloat32Array([seconds])[0]
138
139
  for places: int in range(0, 10):
139
140
  var shorter: float = String.num(held, places).to_float()
@@ -159,7 +160,7 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
159
160
  var keyframes: Array[Dictionary] = []
160
161
  var methods: Dictionary = {}
161
162
  for key: int in anim.track_get_key_count(track):
162
- var keyframe: Dictionary = {"time": key_time(anim.track_get_key_time(track, key))}
163
+ var keyframe: Dictionary = {"time": held_seconds(anim.track_get_key_time(track, key))}
163
164
  if kind == Animation.TYPE_METHOD:
164
165
  var method: String = str(anim.method_track_get_name(track, key))
165
166
  methods[method] = true
@@ -320,7 +320,10 @@ func create_animation(args: Dictionary) -> Dictionary:
320
320
  return "%s would not be in the scene when it loads." % animation_name
321
321
  return ""
322
322
  var answer: Dictionary = {
323
- "ok": true, "animationName": animation_name, "length": anim.length, "loopMode": loop_mode_name
323
+ "ok": true,
324
+ "animationName": animation_name,
325
+ "length": GdhAnimationReads.held_seconds(anim.length),
326
+ "loopMode": loop_mode_name
324
327
  }
325
328
  if library[2]:
326
329
  answer["libraryMade"] = animation_name.get_slice("/", 0) if animation_name.contains("/") else ""
@@ -477,9 +480,9 @@ func set_animation(args: Dictionary) -> Dictionary:
477
480
  var answer: Dictionary = {
478
481
  "ok": true,
479
482
  "animationName": animation_name,
480
- "length": anim.length,
483
+ "length": GdhAnimationReads.held_seconds(anim.length),
481
484
  "loopMode": GdhAnimationReads.LOOP_MODE_NAMES.get(anim.loop_mode, str(anim.loop_mode)),
482
- "step": anim.step,
485
+ "step": GdhAnimationReads.held_seconds(anim.step),
483
486
  }
484
487
  var past: String = _past_length(anim, animation_name)
485
488
  if not past.is_empty():
@@ -444,7 +444,7 @@ static func _keys_at(anim: Animation, calling: Array, times: Array) -> Array:
444
444
  if pair[1] >= 0 and key != pair[1]:
445
445
  continue
446
446
  var time: float = anim.track_get_key_time(index, key)
447
- var _listed: bool = held.append(str(GdhAnimationReads.key_time(time)))
447
+ var _listed: bool = held.append(str(GdhAnimationReads.held_seconds(time)))
448
448
  for given: float in times:
449
449
  if GdhAnimationReads.same_time(time, given):
450
450
  at.append([index, key])
@@ -61,9 +61,11 @@ func _init(host: Node, values: GdhValues) -> void:
61
61
  ## pointer when the game is on one.
62
62
  ##
63
63
  ## For the root window the engine asks Windows where the pointer is, and GetCursorPos refuses a
64
- ## process on a desktop that is not in use: measured on one of gdharness's own, it answered false
65
- ## with access denied and left the point unwritten. The engine does not check, so a game driven
66
- ## there read get_mouse_position() as x 836852416 after a motion to 1310, 314. Nothing here can
64
+ ## process on a desktop that is not in use: on one of gdharness's own it answers false and leaves the
65
+ ## point unwritten, with access denied in the user's window station and with
66
+ ## ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION in the one gdharness's desktop is in. The engine does
67
+ ## not check, so a game driven there read get_mouse_position() as x 836852416 after a motion to
68
+ ## 1310, 314. Nothing here can
67
69
  ## put a pointer on that desktop: Windows moves one only on the desktop in use, and making this one
68
70
  ## that would take the screen. What reads the injected event is right, tooltips included, since the
69
71
  ## engine places a tooltip from the motion that reached the control.
@@ -125,15 +125,16 @@ static func no_animation(player: AnimationPlayer, called: String, animation_name
125
125
 
126
126
  static func _summary(anim: Animation) -> Dictionary:
127
127
  return {
128
- "length": anim.length,
128
+ "length": held_seconds(anim.length),
129
129
  "loopMode": LOOP_MODE_NAMES.get(anim.loop_mode, str(anim.loop_mode)),
130
- "step": anim.step,
130
+ "step": held_seconds(anim.step),
131
131
  }
132
132
 
133
133
 
134
- ## [param seconds], a key's time, as the shortest decimal holding the same 32-bit float. A scene keeps
135
- ## key times as 32-bit floats, so a key put at 0.7 read back as 0.699999988079071.
136
- static func key_time(seconds: float) -> float:
134
+ ## [param seconds], a key's time or an animation's length or step, as the shortest decimal holding
135
+ ## the same 32-bit float. An animation keeps all three as 32-bit floats, so a key put at 0.7 read
136
+ ## back as 0.699999988079071 and a step of 0.1 as 0.100000001490116.
137
+ static func held_seconds(seconds: float) -> float:
137
138
  var held: float = PackedFloat32Array([seconds])[0]
138
139
  for places: int in range(0, 10):
139
140
  var shorter: float = String.num(held, places).to_float()
@@ -159,7 +160,7 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
159
160
  var keyframes: Array[Dictionary] = []
160
161
  var methods: Dictionary = {}
161
162
  for key: int in anim.track_get_key_count(track):
162
- var keyframe: Dictionary = {"time": key_time(anim.track_get_key_time(track, key))}
163
+ var keyframe: Dictionary = {"time": held_seconds(anim.track_get_key_time(track, key))}
163
164
  if kind == Animation.TYPE_METHOD:
164
165
  var method: String = str(anim.method_track_get_name(track, key))
165
166
  methods[method] = true
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.40",
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",