· 6 years ago · Dec 24, 2019, 11:14 PM
1Index: binaries/system/Collada.dll
2===================================================================
3Cannot display: file marked as a binary type.
4svn:mime-type = application/octet-stream
5Index: binaries/system/glooxwrapper.dll
6===================================================================
7Cannot display: file marked as a binary type.
8svn:mime-type = application/octet-stream
9Index: binaries/system/pyrogenesis.exe
10===================================================================
11Cannot display: file marked as a binary type.
12svn:mime-type = application/octet-stream
13Index: libraries/source/fcollada/src/meson.build
14===================================================================
15--- libraries/source/fcollada/src/meson.build (nonexistent)
16+++ libraries/source/fcollada/src/meson.build (working copy)
17@@ -0,0 +1,189 @@
18+cpp_args = ['-DRETAIL']
19+
20+if host_machine.system() == 'linux'
21+ cpp_args += '-DLINUX'
22+endif
23+
24+if get_option('buildtype') == 'release'
25+ cpp_args += '-DNDEBUG'
26+else
27+ cpp_args += '-D_DEBUG'
28+endif
29+
30+# FCollada is not aliasing-safe, so disallow dangerous optimisations
31+# (TODO: It'd be nice to fix FCollada, but that looks hard)
32+cpp_args += '-fno-strict-aliasing'
33+
34+sources = [
35+ 'FCollada/FCollada.cpp',
36+ 'FCollada/FColladaPlugin.cpp',
37+ 'FCollada/FCDocument/FCDAnimated.cpp',
38+ 'FCollada/FCDocument/FCDAnimationChannel.cpp',
39+ 'FCollada/FCDocument/FCDAnimationClip.cpp',
40+ 'FCollada/FCDocument/FCDAnimationClipTools.cpp',
41+ 'FCollada/FCDocument/FCDAnimation.cpp',
42+ 'FCollada/FCDocument/FCDAnimationCurve.cpp',
43+ 'FCollada/FCDocument/FCDAnimationCurveTools.cpp',
44+ 'FCollada/FCDocument/FCDAnimationKey.cpp',
45+ 'FCollada/FCDocument/FCDAnimationMultiCurve.cpp',
46+ 'FCollada/FCDocument/FCDAsset.cpp',
47+ 'FCollada/FCDocument/FCDCamera.cpp',
48+ 'FCollada/FCDocument/FCDController.cpp',
49+ 'FCollada/FCDocument/FCDControllerInstance.cpp',
50+ 'FCollada/FCDocument/FCDControllerTools.cpp',
51+ 'FCollada/FCDocument/FCDEffectCode.cpp',
52+ 'FCollada/FCDocument/FCDEffect.cpp',
53+ 'FCollada/FCDocument/FCDEffectParameter.cpp',
54+ 'FCollada/FCDocument/FCDEffectParameterFactory.cpp',
55+ 'FCollada/FCDocument/FCDEffectParameterSampler.cpp',
56+ 'FCollada/FCDocument/FCDEffectParameterSurface.cpp',
57+ 'FCollada/FCDocument/FCDEffectPass.cpp',
58+ 'FCollada/FCDocument/FCDEffectPassShader.cpp',
59+ 'FCollada/FCDocument/FCDEffectPassState.cpp',
60+ 'FCollada/FCDocument/FCDEffectProfile.cpp',
61+ 'FCollada/FCDocument/FCDEffectProfileFX.cpp',
62+ 'FCollada/FCDocument/FCDEffectStandard.cpp',
63+ 'FCollada/FCDocument/FCDEffectTechnique.cpp',
64+ 'FCollada/FCDocument/FCDEffectTools.cpp',
65+ 'FCollada/FCDocument/FCDEmitter.cpp',
66+ 'FCollada/FCDocument/FCDEmitterInstance.cpp',
67+ 'FCollada/FCDocument/FCDEmitterObject.cpp',
68+ 'FCollada/FCDocument/FCDEmitterParticle.cpp',
69+ 'FCollada/FCDocument/FCDEntity.cpp',
70+ 'FCollada/FCDocument/FCDEntityInstance.cpp',
71+ 'FCollada/FCDocument/FCDEntityReference.cpp',
72+ 'FCollada/FCDocument/FCDExternalReferenceManager.cpp',
73+ 'FCollada/FCDocument/FCDExtra.cpp',
74+ 'FCollada/FCDocument/FCDForceDeflector.cpp',
75+ 'FCollada/FCDocument/FCDForceDrag.cpp',
76+ 'FCollada/FCDocument/FCDForceField.cpp',
77+ 'FCollada/FCDocument/FCDForceGravity.cpp',
78+ 'FCollada/FCDocument/FCDForcePBomb.cpp',
79+ 'FCollada/FCDocument/FCDForceWind.cpp',
80+ 'FCollada/FCDocument/FCDGeometry.cpp',
81+ 'FCollada/FCDocument/FCDGeometryInstance.cpp',
82+ 'FCollada/FCDocument/FCDGeometryMesh.cpp',
83+ 'FCollada/FCDocument/FCDGeometryNURBSSurface.cpp',
84+ 'FCollada/FCDocument/FCDGeometryPolygons.cpp',
85+ 'FCollada/FCDocument/FCDGeometryPolygonsInput.cpp',
86+ 'FCollada/FCDocument/FCDGeometryPolygonsTools.cpp',
87+ 'FCollada/FCDocument/FCDGeometrySource.cpp',
88+ 'FCollada/FCDocument/FCDGeometrySpline.cpp',
89+ 'FCollada/FCDocument/FCDImage.cpp',
90+ 'FCollada/FCDocument/FCDLibrary.cpp',
91+ 'FCollada/FCDocument/FCDLight.cpp',
92+ 'FCollada/FCDocument/FCDLightTools.cpp',
93+ 'FCollada/FCDocument/FCDMaterial.cpp',
94+ 'FCollada/FCDocument/FCDMaterialInstance.cpp',
95+ 'FCollada/FCDocument/FCDMorphController.cpp',
96+ 'FCollada/FCDocument/FCDObject.cpp',
97+ 'FCollada/FCDocument/FCDObjectWithId.cpp',
98+ 'FCollada/FCDocument/FCDocument.cpp',
99+ 'FCollada/FCDocument/FCDocumentTools.cpp',
100+ 'FCollada/FCDocument/FCDParameterAnimatable.cpp',
101+ 'FCollada/FCDocument/FCDParticleModifier.cpp',
102+ 'FCollada/FCDocument/FCDPhysicsAnalyticalGeometry.cpp',
103+ 'FCollada/FCDocument/FCDPhysicsForceFieldInstance.cpp',
104+ 'FCollada/FCDocument/FCDPhysicsMaterial.cpp',
105+ 'FCollada/FCDocument/FCDPhysicsModel.cpp',
106+ 'FCollada/FCDocument/FCDPhysicsModelInstance.cpp',
107+ 'FCollada/FCDocument/FCDPhysicsRigidBody.cpp',
108+ 'FCollada/FCDocument/FCDPhysicsRigidBodyInstance.cpp',
109+ 'FCollada/FCDocument/FCDPhysicsRigidBodyParameters.cpp',
110+ 'FCollada/FCDocument/FCDPhysicsRigidConstraint.cpp',
111+ 'FCollada/FCDocument/FCDPhysicsRigidConstraintInstance.cpp',
112+ 'FCollada/FCDocument/FCDPhysicsScene.cpp',
113+ 'FCollada/FCDocument/FCDPhysicsShape.cpp',
114+ 'FCollada/FCDocument/FCDPlaceHolder.cpp',
115+ 'FCollada/FCDocument/FCDSceneNode.cpp',
116+ 'FCollada/FCDocument/FCDSceneNodeIterator.cpp',
117+ 'FCollada/FCDocument/FCDSceneNodeTools.cpp',
118+ 'FCollada/FCDocument/FCDSkinController.cpp',
119+ 'FCollada/FCDocument/FCDTargetedEntity.cpp',
120+ 'FCollada/FCDocument/FCDTexture.cpp',
121+ 'FCollada/FCDocument/FCDTransform.cpp',
122+ 'FCollada/FCDocument/FCDVersion.cpp',
123+ 'FCollada/FMath/FMAllocator.cpp',
124+ 'FCollada/FMath/FMAngleAxis.cpp',
125+ 'FCollada/FMath/FMColor.cpp',
126+ 'FCollada/FMath/FMInterpolation.cpp',
127+ 'FCollada/FMath/FMLookAt.cpp',
128+ 'FCollada/FMath/FMMatrix33.cpp',
129+ 'FCollada/FMath/FMMatrix44.cpp',
130+ 'FCollada/FMath/FMQuaternion.cpp',
131+ 'FCollada/FMath/FMRandom.cpp',
132+ 'FCollada/FMath/FMSkew.cpp',
133+ 'FCollada/FMath/FMVector3.cpp',
134+ 'FCollada/FMath/FMVolume.cpp',
135+ 'FCollada/FUtils/FUAssert.cpp',
136+ 'FCollada/FUtils/FUBase64.cpp',
137+ 'FCollada/FUtils/FUBoundingBox.cpp',
138+ 'FCollada/FUtils/FUBoundingSphere.cpp',
139+ 'FCollada/FUtils/FUCrc32.cpp',
140+ 'FCollada/FUtils/FUCriticalSection.cpp',
141+ 'FCollada/FUtils/FUDaeEnum.cpp',
142+ 'FCollada/FUtils/FUDateTime.cpp',
143+ 'FCollada/FUtils/FUDebug.cpp',
144+ 'FCollada/FUtils/FUError.cpp',
145+ 'FCollada/FUtils/FUErrorLog.cpp',
146+ 'FCollada/FUtils/FUFile.cpp',
147+ 'FCollada/FUtils/FUFileManager.cpp',
148+ 'FCollada/FUtils/FULogFile.cpp',
149+ 'FCollada/FUtils/FUObject.cpp',
150+ 'FCollada/FUtils/FUObjectType.cpp',
151+ 'FCollada/FUtils/FUParameter.cpp',
152+ 'FCollada/FUtils/FUParameterizable.cpp',
153+ 'FCollada/FUtils/FUPluginManager.cpp',
154+ 'FCollada/FUtils/FUSemaphore.cpp',
155+ 'FCollada/FUtils/FUStringBuilder.cpp',
156+ 'FCollada/FUtils/FUStringConversion.cpp',
157+ 'FCollada/FUtils/FUSynchronizableObject.cpp',
158+ 'FCollada/FUtils/FUThread.cpp',
159+ 'FCollada/FUtils/FUTracker.cpp',
160+ 'FCollada/FUtils/FUUniqueStringMap.cpp',
161+ 'FCollada/FUtils/FUUri.cpp',
162+ 'FCollada/FUtils/FUXmlDocument.cpp',
163+ 'FCollada/FUtils/FUXmlParser.cpp',
164+ 'FCollada/FUtils/FUXmlWriter.cpp',
165+ 'FColladaPlugins/FArchiveXML/FArchiveXML.cpp',
166+ 'FColladaPlugins/FArchiveXML/FAXAnimationExport.cpp',
167+ 'FColladaPlugins/FArchiveXML/FAXAnimationImport.cpp',
168+ 'FColladaPlugins/FArchiveXML/FAXCameraExport.cpp',
169+ 'FColladaPlugins/FArchiveXML/FAXCameraImport.cpp',
170+ 'FColladaPlugins/FArchiveXML/FAXColladaParser.cpp',
171+ 'FColladaPlugins/FArchiveXML/FAXColladaWriter.cpp',
172+ 'FColladaPlugins/FArchiveXML/FAXControllerExport.cpp',
173+ 'FColladaPlugins/FArchiveXML/FAXControllerImport.cpp',
174+ 'FColladaPlugins/FArchiveXML/FAXEmitterExport.cpp',
175+ 'FColladaPlugins/FArchiveXML/FAXEmitterImport.cpp',
176+ 'FColladaPlugins/FArchiveXML/FAXEntityExport.cpp',
177+ 'FColladaPlugins/FArchiveXML/FAXEntityImport.cpp',
178+ 'FColladaPlugins/FArchiveXML/FAXForceFieldExport.cpp',
179+ 'FColladaPlugins/FArchiveXML/FAXForceFieldImport.cpp',
180+ 'FColladaPlugins/FArchiveXML/FAXGeometryExport.cpp',
181+ 'FColladaPlugins/FArchiveXML/FAXGeometryImport.cpp',
182+ 'FColladaPlugins/FArchiveXML/FAXImportLinking.cpp',
183+ 'FColladaPlugins/FArchiveXML/FAXInstanceExport.cpp',
184+ 'FColladaPlugins/FArchiveXML/FAXInstanceImport.cpp',
185+ 'FColladaPlugins/FArchiveXML/FAXLightExport.cpp',
186+ 'FColladaPlugins/FArchiveXML/FAXLightImport.cpp',
187+ 'FColladaPlugins/FArchiveXML/FAXMaterialExport.cpp',
188+ 'FColladaPlugins/FArchiveXML/FAXMaterialImport.cpp',
189+ 'FColladaPlugins/FArchiveXML/FAXPhysicsExport.cpp',
190+ 'FColladaPlugins/FArchiveXML/FAXPhysicsImport.cpp',
191+ 'FColladaPlugins/FArchiveXML/FAXSceneExport.cpp',
192+ 'FColladaPlugins/FArchiveXML/FAXSceneImport.cpp',
193+]
194+
195+fcollada_lib = static_library(
196+ 'FCollada',
197+ sources,
198+ include_directories: 'FCollada',
199+ cpp_args: cpp_args,
200+ dependencies: [dep_dl, dep_libxml2]
201+)
202+
203+dep_fcollada = declare_dependency(
204+ include_directories: ['../include'],
205+ link_with: fcollada_lib,
206+)
207Index: libraries/source/nvtt/meson.build
208===================================================================
209--- libraries/source/nvtt/meson.build (nonexistent)
210+++ libraries/source/nvtt/meson.build (working copy)
211@@ -0,0 +1,68 @@
212+#cmake .. -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=Release -DBINDIR=bin -DLIBDIR=lib -DGLUT=0 -DGLEW=0 -DCG=0 -DCUDA=0 -DOPENEXR=0 -G "Unix Makefiles"
213+
214+sources = [
215+ 'src/src/nvcore/Debug.cpp',
216+ 'src/src/nvcore/Library.cpp',
217+ 'src/src/nvcore/Memory.cpp',
218+ 'src/src/nvcore/Radix.cpp',
219+ 'src/src/nvcore/StrLib.cpp',
220+ 'src/src/nvcore/TextReader.cpp',
221+ 'src/src/nvcore/TextWriter.cpp',
222+ 'src/src/nvcore/Tokenizer.cpp',
223+ 'src/src/nvimage/BlockDXT.cpp',
224+ 'src/src/nvimage/ColorBlock.cpp',
225+ 'src/src/nvimage/DirectDrawSurface.cpp',
226+ 'src/src/nvimage/Filter.cpp',
227+ 'src/src/nvimage/FloatImage.cpp',
228+ 'src/src/nvimage/HoleFilling.cpp',
229+ 'src/src/nvimage/Image.cpp',
230+ 'src/src/nvimage/ImageIO.cpp',
231+ 'src/src/nvimage/NormalMap.cpp',
232+ 'src/src/nvimage/NormalMipmap.cpp',
233+ 'src/src/nvimage/Quantize.cpp',
234+ 'src/src/nvmath/Basis.cpp',
235+ 'src/src/nvmath/Montecarlo.cpp',
236+ 'src/src/nvmath/Plane.cpp',
237+ 'src/src/nvmath/Random.cpp',
238+ 'src/src/nvmath/SphericalHarmonic.cpp',
239+ 'src/src/nvmath/Triangle.cpp',
240+ 'src/src/nvmath/TriBox.cpp',
241+ 'src/src/nvtt/CompressDXT.cpp',
242+ 'src/src/nvtt/CompressionOptions.cpp',
243+ 'src/src/nvtt/Compressor.cpp',
244+ 'src/src/nvtt/CompressRGB.cpp',
245+ 'src/src/nvtt/cuda/CudaCompressDXT.cpp',
246+ 'src/src/nvtt/cuda/CudaUtils.cpp',
247+ 'src/src/nvtt/InputOptions.cpp',
248+ 'src/src/nvtt/nvtt.cpp',
249+ 'src/src/nvtt/nvtt_wrapper.cpp',
250+ 'src/src/nvtt/OptimalCompressDXT.cpp',
251+ 'src/src/nvtt/OutputOptions.cpp',
252+ 'src/src/nvtt/QuickCompressDXT.cpp',
253+ 'src/src/nvtt/squish/colourblock.cpp',
254+ 'src/src/nvtt/squish/colourfit.cpp',
255+ 'src/src/nvtt/squish/colourset.cpp',
256+ 'src/src/nvtt/squish/maths.cpp',
257+ 'src/src/nvtt/squish/weightedclusterfit.cpp',
258+]
259+
260+config_h = configuration_data()
261+config_h.set('HAVE_UNISTD_H', 1)
262+config_h.set('HAVE_STDARG_H', 1)
263+config_h.set('HAVE_SIGNAL_H', 1)
264+config_h.set('HAVE_EXECINFO_H', 1)
265+config_h.set('HAVE_MALLOC_H', 1)
266+config_h.set('HAVE_PNG', 1)
267+config_h.set('POSH_USE_LIMITS_H', 1)
268+configure_file(output: 'nvconfig.h', configuration: config_h)
269+
270+nvtt_lib = static_library(
271+ 'nvtt',
272+ sources,
273+ include_directories: ['src/src', 'src/src/nvtt/squish'],
274+)
275+
276+dep_nvtt = declare_dependency(
277+ include_directories: ['include'],
278+ link_with: nvtt_lib,
279+)
280Index: libraries/win32/wxwidgets
281===================================================================
282--- libraries/win32/wxwidgets (revision 23275)
283+++ libraries/win32/wxwidgets (working copy)
284
285Property changes on: libraries/win32/wxwidgets
286___________________________________________________________________
287Added: svn:global-ignores
288## -0,0 +1,2 ##
289+include
290+lib
291Index: meson.build
292===================================================================
293--- meson.build (nonexistent)
294+++ meson.build (working copy)
295@@ -0,0 +1,207 @@
296+project('0ad',
297+ 'cpp',
298+ version: 'a23.1',
299+ default_options: [
300+ 'warning_level=0',
301+ ],
302+ meson_version: '>= 0.52',
303+ license: 'GPL',
304+)
305+
306+if get_option('gles')
307+ dep_gl = dependency('glesv2')
308+else
309+ dep_gl = dependency('gl')
310+endif
311+
312+dep_tinygettext = declare_dependency(
313+ include_directories: 'source/third_party/tinygettext/include',
314+)
315+
316+dep_valgrind = declare_dependency(
317+ include_directories: 'libraries/source/valgrind/include',
318+)
319+
320+dep_cxxtest = declare_dependency(
321+ include_directories: 'libraries/source/cxxtest-4.4',
322+)
323+
324+cc = meson.get_compiler('cpp')
325+
326+if host_machine.system() != 'windows'
327+ dep_dl = cc.find_library('dl', required: false)
328+ dep_boost = cc.find_library('boost_filesystem')
329+ dep_enet = dependency('libenet')
330+
331+
332+ if get_option('lobby')
333+ dep_gloox = dependency('gloox')
334+ endif
335+
336+ dep_iconv = []
337+ dep_icu = dependency('icu-i18n')
338+ dep_curl = dependency('libcurl')
339+ dep_png = dependency('libpng')
340+ dep_sodium = dependency('libsodium')
341+ dep_libxml2 = dependency('libxml-2.0')
342+
343+ if get_option('upnp')
344+ dep_miniupnpc = dependency('miniupnpc')
345+ endif
346+
347+ dep_sdl2 = dependency('SDL2')
348+
349+ if get_option('audio')
350+ dep_openal = dependency('openal')
351+ dep_vorbis = dependency('vorbisfile')
352+ endif
353+
354+ dep_zlib = dependency('zlib')
355+
356+ dep_js = declare_dependency(
357+ include_directories: 'libraries/source/spidermonkey/include-unix-release',
358+ dependencies: cc.find_library('js_static', dirs: meson.current_source_dir() / 'libraries/source/spidermonkey/mozjs-45.0.2/js/src/build-release/js/src'),
359+ )
360+ spidermonkey_include = 'libraries/source/spidermonkey/include-win32-release'
361+
362+ subdir('libraries/source/fcollada/src')
363+
364+ if get_option('nvtt')
365+ subdir('libraries/source/nvtt')
366+ endif
367+else
368+
369+ dep_gl = declare_dependency(
370+ include_directories: 'libraries/win32/opengl/include'
371+ )
372+
373+ if get_option('buildtype') == 'release'
374+ boost_dependencies = [
375+ cc.find_library('libboost_filesystem-vc140-mt-1_65_1',
376+ dirs: meson.current_source_dir() / 'libraries/win32/boost/lib/'
377+ ),
378+ cc.find_library('libboost_system-vc140-mt-1_65_1',
379+ dirs: meson.current_source_dir() / 'libraries/win32/boost/lib/'
380+ ),
381+ ]
382+ else
383+ boost_dependencies = [
384+ cc.find_library('libboost_filesystem-vc140-mt-gd-1_65_1',
385+ dirs: meson.current_source_dir() / 'libraries/win32/boost/lib/'
386+ ),
387+ cc.find_library('libboost_system-vc140-mt-gd-1_65_1',
388+ dirs: meson.current_source_dir() / 'libraries/win32/boost/lib/'
389+ ),
390+ ]
391+ endif
392+
393+ dep_boost = declare_dependency(
394+ include_directories: 'libraries/win32/boost/include',
395+ dependencies: boost_dependencies
396+ )
397+
398+ dep_enet = declare_dependency(
399+ include_directories: 'libraries/win32/enet/include',
400+ dependencies: cc.find_library('enet', dirs: meson.current_source_dir() / 'libraries/win32/enet/lib'),
401+ )
402+
403+ if get_option('lobby')
404+ dep_gloox = declare_dependency(
405+ include_directories: 'libraries/win32/gloox/include',
406+ dependencies: cc.find_library('gloox-1.0', dirs: meson.current_source_dir() / 'libraries/win32/gloox/lib'),
407+ )
408+ endif
409+
410+ dep_iconv = declare_dependency(
411+ include_directories: 'libraries/win32/iconv/include',
412+ dependencies: cc.find_library('libiconv', dirs: meson.current_source_dir() / 'libraries/win32/iconv/lib'),
413+ )
414+
415+ dep_icu = declare_dependency(
416+ include_directories: 'libraries/win32/icu/include',
417+ dependencies: [
418+ cc.find_library('icudt', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
419+ cc.find_library('icuin', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
420+ cc.find_library('icuio', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
421+ cc.find_library('icule', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
422+ cc.find_library('iculx', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
423+ cc.find_library('icutu', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
424+ cc.find_library('icuuc', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
425+ ],
426+ )
427+
428+ dep_curl = declare_dependency(
429+ include_directories: 'libraries/win32/libcurl/include',
430+ dependencies: cc.find_library('libcurl', dirs: meson.current_source_dir() / 'libraries/win32/libcurl/lib'),
431+ )
432+
433+ dep_png = declare_dependency(
434+ include_directories: 'libraries/win32/libpng/include',
435+ dependencies: cc.find_library('libpng16', dirs: meson.current_source_dir() / 'libraries/win32/libpng/lib'),
436+ )
437+
438+ dep_sodium = declare_dependency(
439+ include_directories: 'libraries/win32/libsodium/include',
440+ dependencies: cc.find_library('libsodium', dirs: meson.current_source_dir() / 'libraries/win32/libsodium/lib'),
441+ )
442+
443+ dep_libxml2 = declare_dependency(
444+ include_directories: 'libraries/win32/libxml2/include',
445+ dependencies: cc.find_library('libxml2', dirs: meson.current_source_dir() / 'libraries/win32/libxml2/lib'),
446+ )
447+
448+ if get_option('upnp')
449+ dep_miniupnpc = declare_dependency(
450+ include_directories: 'libraries/win32/miniupnpc/include',
451+ dependencies: cc.find_library('miniupnpc', dirs: meson.current_source_dir() / 'libraries/win32/miniupnpc/lib'),
452+ )
453+ endif
454+
455+ dep_sdl2 = declare_dependency(
456+ include_directories: 'libraries/win32/sdl2/include/SDL',
457+ dependencies: [
458+ cc.find_library('SDL2', dirs: meson.current_source_dir() / 'libraries/win32/sdl2/lib'),
459+ cc.find_library('SDL2main', dirs: meson.current_source_dir() / 'libraries/win32/sdl2/lib'),
460+ ]
461+ )
462+
463+ if get_option('audio')
464+ dep_openal = declare_dependency(
465+ include_directories: 'libraries/win32/openal/include',
466+ dependencies: cc.find_library('OpenAL32', dirs: meson.current_source_dir() / 'libraries/win32/openal/lib'),
467+ )
468+
469+ dep_vorbis = declare_dependency(
470+ include_directories: 'libraries/win32/vorbis/include',
471+ dependencies: [
472+ cc.find_library('libogg', dirs: meson.current_source_dir() / 'libraries/win32/vorbis/lib'),
473+ cc.find_library('libvorbis', dirs: meson.current_source_dir() / 'libraries/win32/vorbis/lib'),
474+ cc.find_library('libvorbisfile', dirs: meson.current_source_dir() / 'libraries/win32/vorbis/lib'),
475+ ]
476+ )
477+ endif
478+
479+ dep_zlib = declare_dependency(
480+ include_directories: 'libraries/win32/zlib/include',
481+ dependencies: cc.find_library('zlib1', dirs: meson.current_source_dir() / 'libraries/win32/zlib/lib/'),
482+ )
483+
484+ dep_fcollada = declare_dependency(
485+ include_directories: 'libraries/source/fcollada/include',
486+ dependencies: cc.find_library('FCollada', dirs: meson.current_source_dir() / 'libraries/source/fcollada/lib'),
487+ )
488+
489+ dep_js = declare_dependency(
490+ include_directories: 'libraries/source/spidermonkey/include-win32-release',
491+ dependencies: cc.find_library('mozjs45-ps-release-vc140', dirs: meson.current_source_dir() / 'libraries/source/spidermonkey/lib'),
492+ )
493+
494+ if get_option('nvtt')
495+ dep_nvtt = declare_dependency(
496+ include_directories: 'libraries/source/nvtt/include',
497+ dependencies: cc.find_library('nvtt', dirs: meson.current_source_dir() / 'libraries/source/nvtt/lib')
498+ )
499+ endif
500+ endif
501+
502+subdir('source')
503Index: meson_options.txt
504===================================================================
505--- meson_options.txt (nonexistent)
506+++ meson_options.txt (working copy)
507@@ -0,0 +1,48 @@
508+option(
509+ 'gles',
510+ type: 'boolean',
511+ value: false,
512+ description: 'Build with OpenGL ES support instead of OpenGL'
513+)
514+
515+option(
516+ 'audio',
517+ type: 'boolean',
518+ value: true,
519+ description: 'Build with audio support'
520+)
521+
522+option(
523+ 'lobby',
524+ type: 'boolean',
525+ value: true,
526+ description: 'Build with XMPP support'
527+)
528+
529+option(
530+ 'upnp',
531+ type: 'boolean',
532+ value: true,
533+ description: 'Build with UPnP support'
534+)
535+
536+option(
537+ 'nvtt',
538+ type: 'boolean',
539+ value: true,
540+ description: 'Build with NVTT support'
541+)
542+
543+option(
544+ 'atlas',
545+ type: 'boolean',
546+ value: false,
547+ description: 'Build Atlas the map editor'
548+)
549+
550+option(
551+ 'collada',
552+ type: 'boolean',
553+ value: true,
554+ description: 'Build the collada wrapper'
555+)
556Index: source/gui/ObjectTypes/CInput.cpp
557===================================================================
558--- source/gui/ObjectTypes/CInput.cpp (revision 23275)
559+++ source/gui/ObjectTypes/CInput.cpp (working copy)
560@@ -1,2051 +1,2052 @@
561 /* Copyright (C) 2019 Wildfire Games.
562 * This file is part of 0 A.D.
563 *
564 * 0 A.D. is free software: you can redistribute it and/or modify
565 * it under the terms of the GNU General Public License as published by
566 * the Free Software Foundation, either version 2 of the License, or
567 * (at your option) any later version.
568 *
569 * 0 A.D. is distributed in the hope that it will be useful,
570 * but WITHOUT ANY WARRANTY; without even the implied warranty of
571 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
572 * GNU General Public License for more details.
573 *
574 * You should have received a copy of the GNU General Public License
575 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
576 */
577
578 #include "precompiled.h"
579
580 #include "CInput.h"
581
582 #include "graphics/FontMetrics.h"
583 #include "graphics/ShaderManager.h"
584 #include "graphics/TextRenderer.h"
585 #include "gui/CGUI.h"
586 #include "gui/CGUIScrollBarVertical.h"
587-#include "lib/sysdep/clipboard.h"
588 #include "lib/timer.h"
589 #include "lib/utf8.h"
590 #include "ps/ConfigDB.h"
591 #include "ps/GameSetup/Config.h"
592 #include "ps/Globals.h"
593 #include "ps/Hotkey.h"
594 #include "renderer/Renderer.h"
595
596 #include <sstream>
597
598 extern int g_yres;
599
600 CInput::CInput(CGUI& pGUI)
601 :
602 IGUIObject(pGUI),
603 IGUIScrollBarOwner(*static_cast<IGUIObject*>(this)),
604 m_iBufferPos(-1),
605 m_iBufferPos_Tail(-1),
606 m_SelectingText(),
607 m_HorizontalScroll(),
608 m_PrevTime(),
609 m_CursorVisState(true),
610 m_CursorBlinkRate(0.5),
611 m_ComposingText(),
612 m_iComposedLength(),
613 m_iComposedPos(),
614 m_iInsertPos(),
615 m_BufferPosition(),
616 m_BufferZone(),
617 m_Caption(),
618 m_CellID(),
619 m_Font(),
620 m_MaskChar(),
621 m_Mask(),
622 m_MaxLength(),
623 m_MultiLine(),
624 m_Readonly(),
625 m_ScrollBar(),
626 m_ScrollBarStyle(),
627 m_Sprite(),
628 m_SpriteSelectArea(),
629 m_TextColor(),
630 m_TextColorSelected()
631 {
632 RegisterSetting("buffer_position", m_BufferPosition);
633 RegisterSetting("buffer_zone", m_BufferZone);
634 RegisterSetting("caption", m_Caption);
635 RegisterSetting("cell_id", m_CellID);
636 RegisterSetting("font", m_Font);
637 RegisterSetting("mask_char", m_MaskChar);
638 RegisterSetting("mask", m_Mask);
639 RegisterSetting("max_length", m_MaxLength);
640 RegisterSetting("multiline", m_MultiLine);
641 RegisterSetting("readonly", m_Readonly);
642 RegisterSetting("scrollbar", m_ScrollBar);
643 RegisterSetting("scrollbar_style", m_ScrollBarStyle);
644 RegisterSetting("sprite", m_Sprite);
645 RegisterSetting("sprite_selectarea", m_SpriteSelectArea);
646 RegisterSetting("textcolor", m_TextColor);
647 RegisterSetting("textcolor_selected", m_TextColorSelected);
648
649 CFG_GET_VAL("gui.cursorblinkrate", m_CursorBlinkRate);
650
651 CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI);
652 bar->SetRightAligned(true);
653 AddScrollBar(bar);
654 }
655
656 CInput::~CInput()
657 {
658 }
659
660 void CInput::UpdateBufferPositionSetting()
661 {
662 SetSetting<i32>("buffer_position", m_iBufferPos, false);
663 }
664
665 void CInput::ClearComposedText()
666 {
667 m_Caption.erase(m_iInsertPos, m_iComposedLength);
668 m_iBufferPos = m_iInsertPos;
669 UpdateBufferPositionSetting();
670 m_iComposedLength = 0;
671 m_iComposedPos = 0;
672 }
673
674 InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
675 {
676 ENSURE(m_iBufferPos != -1);
677
678 switch (ev->ev.type)
679 {
680 case SDL_HOTKEYDOWN:
681 {
682 if (m_ComposingText)
683 return IN_HANDLED;
684
685 return ManuallyHandleHotkeyEvent(ev);
686 }
687 // SDL2 has a new method of text input that better supports Unicode and CJK
688 // see https://wiki.libsdl.org/Tutorials/TextInput
689 case SDL_TEXTINPUT:
690 {
691 if (m_Readonly)
692 return IN_PASS;
693
694 // Text has been committed, either single key presses or through an IME
695 std::wstring text = wstring_from_utf8(ev->ev.text.text);
696
697 m_WantedX = 0.0f;
698
699 if (SelectingText())
700 DeleteCurSelection();
701
702 if (m_ComposingText)
703 {
704 ClearComposedText();
705 m_ComposingText = false;
706 }
707
708 if (m_iBufferPos == static_cast<int>(m_Caption.length()))
709 m_Caption.append(text);
710 else
711 m_Caption.insert(m_iBufferPos, text);
712
713 UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
714
715 m_iBufferPos += text.length();
716 UpdateBufferPositionSetting();
717 m_iBufferPos_Tail = -1;
718
719 UpdateAutoScroll();
720 SendEvent(GUIM_TEXTEDIT, "textedit");
721
722 return IN_HANDLED;
723 }
724 case SDL_TEXTEDITING:
725 {
726 if (m_Readonly)
727 return IN_PASS;
728
729 // Text is being composed with an IME
730 // TODO: indicate this by e.g. underlining the uncommitted text
731 const char* rawText = ev->ev.edit.text;
732 int rawLength = strlen(rawText);
733 std::wstring wtext = wstring_from_utf8(rawText);
734
735 debug_printf("SDL_TEXTEDITING: text=%s, start=%d, length=%d\n", rawText, ev->ev.edit.start, ev->ev.edit.length);
736 m_WantedX = 0.0f;
737
738 if (SelectingText())
739 DeleteCurSelection();
740
741 // Remember cursor position when text composition begins
742 if (!m_ComposingText)
743 m_iInsertPos = m_iBufferPos;
744 else
745 {
746 // Composed text is replaced each time
747 ClearComposedText();
748 }
749
750 m_ComposingText = ev->ev.edit.start != 0 || rawLength != 0;
751 if (m_ComposingText)
752 {
753 m_Caption.insert(m_iInsertPos, wtext);
754
755 // The text buffer is limited to SDL_TEXTEDITINGEVENT_TEXT_SIZE bytes, yet start
756 // increases without limit, so don't let it advance beyond the composed text length
757 m_iComposedLength = wtext.length();
758 m_iComposedPos = ev->ev.edit.start < m_iComposedLength ? ev->ev.edit.start : m_iComposedLength;
759 m_iBufferPos = m_iInsertPos + m_iComposedPos;
760
761 // TODO: composed text selection - what does ev.edit.length do?
762 m_iBufferPos_Tail = -1;
763 }
764
765 UpdateBufferPositionSetting();
766 UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
767
768 UpdateAutoScroll();
769 SendEvent(GUIM_TEXTEDIT, "textedit");
770
771 return IN_HANDLED;
772 }
773 case SDL_KEYDOWN:
774 {
775 if (m_ComposingText)
776 return IN_HANDLED;
777
778 // Since the GUI framework doesn't handle to set settings
779 // in Unicode (CStrW), we'll simply retrieve the actual
780 // pointer and edit that.
781 SDL_Keycode keyCode = ev->ev.key.keysym.sym;
782
783 ManuallyImmutableHandleKeyDownEvent(keyCode);
784 ManuallyMutableHandleKeyDownEvent(keyCode);
785
786 UpdateBufferPositionSetting();
787 return IN_HANDLED;
788 }
789 default:
790 {
791 return IN_PASS;
792 }
793 }
794 }
795
796 void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode)
797 {
798 if (m_Readonly)
799 return;
800
801 wchar_t cooked = 0;
802
803 switch (keyCode)
804 {
805 case SDLK_TAB:
806 {
807 SendEvent(GUIM_TAB, "tab");
808 // Don't send a textedit event, because it should only
809 // be sent if the GUI control changes the text
810 break;
811 }
812 case SDLK_BACKSPACE:
813 {
814 m_WantedX = 0.0f;
815
816 if (SelectingText())
817 DeleteCurSelection();
818 else
819 {
820 m_iBufferPos_Tail = -1;
821
822 if (m_Caption.empty() || m_iBufferPos == 0)
823 break;
824
825 if (m_iBufferPos == static_cast<int>(m_Caption.length()))
826 m_Caption = m_Caption.Left(static_cast<long>(m_Caption.length()) - 1);
827 else
828 m_Caption =
829 m_Caption.Left(m_iBufferPos - 1) +
830 m_Caption.Right(static_cast<long>(m_Caption.length()) - m_iBufferPos);
831
832 --m_iBufferPos;
833
834 UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos);
835 }
836
837 UpdateAutoScroll();
838 SendEvent(GUIM_TEXTEDIT, "textedit");
839 break;
840 }
841 case SDLK_DELETE:
842 {
843 m_WantedX = 0.0f;
844
845 if (SelectingText())
846 DeleteCurSelection();
847 else
848 {
849 if (m_Caption.empty() || m_iBufferPos == static_cast<int>(m_Caption.length()))
850 break;
851
852 m_Caption =
853 m_Caption.Left(m_iBufferPos) +
854 m_Caption.Right(static_cast<long>(m_Caption.length()) - (m_iBufferPos + 1));
855
856 UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos);
857 }
858
859 UpdateAutoScroll();
860 SendEvent(GUIM_TEXTEDIT, "textedit");
861 break;
862 }
863 case SDLK_KP_ENTER:
864 case SDLK_RETURN:
865 {
866 // 'Return' should do a Press event for single liners (e.g. submitting forms)
867 // otherwise a '\n' character will be added.
868 if (!m_MultiLine)
869 {
870 SendEvent(GUIM_PRESSED, "press");
871 break;
872 }
873
874 cooked = '\n'; // Change to '\n' and do default:
875 FALLTHROUGH;
876 }
877 default: // Insert a character
878 {
879 // In SDL2, we no longer get Unicode wchars via SDL_Keysym
880 // we use text input events instead and they provide UTF-8 chars
881 if (cooked == 0)
882 return;
883
884 // check max length
885 if (m_MaxLength != 0 && static_cast<int>(m_Caption.length()) >= m_MaxLength)
886 break;
887
888 m_WantedX = 0.0f;
889
890 if (SelectingText())
891 DeleteCurSelection();
892 m_iBufferPos_Tail = -1;
893
894 if (m_iBufferPos == static_cast<int>(m_Caption.length()))
895 m_Caption += cooked;
896 else
897 m_Caption =
898 m_Caption.Left(m_iBufferPos) + cooked +
899 m_Caption.Right(static_cast<long>(m_Caption.length()) - m_iBufferPos);
900
901 UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos + 1);
902
903 ++m_iBufferPos;
904
905 UpdateAutoScroll();
906 SendEvent(GUIM_TEXTEDIT, "textedit");
907 break;
908 }
909 }
910 }
911
912 void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode)
913 {
914 bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT];
915
916 switch (keyCode)
917 {
918 case SDLK_HOME:
919 {
920 // If there's not a selection, we should create one now
921 if (!shiftKeyPressed)
922 {
923 // Make sure a selection isn't created.
924 m_iBufferPos_Tail = -1;
925 }
926 else if (!SelectingText())
927 {
928 // Place tail at the current point:
929 m_iBufferPos_Tail = m_iBufferPos;
930 }
931
932 m_iBufferPos = 0;
933 m_WantedX = 0.0f;
934
935 UpdateAutoScroll();
936 break;
937 }
938 case SDLK_END:
939 {
940 // If there's not a selection, we should create one now
941 if (!shiftKeyPressed)
942 {
943 // Make sure a selection isn't created.
944 m_iBufferPos_Tail = -1;
945 }
946 else if (!SelectingText())
947 {
948 // Place tail at the current point:
949 m_iBufferPos_Tail = m_iBufferPos;
950 }
951
952 m_iBufferPos = static_cast<long>(m_Caption.length());
953 m_WantedX = 0.0f;
954
955 UpdateAutoScroll();
956 break;
957 }
958 /**
959 * Conventions for Left/Right when text is selected:
960 *
961 * References:
962 *
963 * Visual Studio
964 * Visual Studio has the 'newer' approach, used by newer versions of
965 * things, and in newer applications. A left press will always place
966 * the pointer on the left edge of the selection, and then of course
967 * remove the selection. Right will do the exact same thing.
968 * If you have the pointer on the right edge and press right, it will
969 * in other words just remove the selection.
970 *
971 * Windows (eg. Notepad)
972 * A left press always takes the pointer a step to the left and
973 * removes the selection as if it were never there in the first place.
974 * Right of course does the same thing but to the right.
975 *
976 * I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN
977 * Messenger.
978 */
979 case SDLK_LEFT:
980 {
981 m_WantedX = 0.f;
982
983 if (shiftKeyPressed || !SelectingText())
984 {
985 if (!shiftKeyPressed)
986 m_iBufferPos_Tail = -1;
987 else if (!SelectingText())
988 m_iBufferPos_Tail = m_iBufferPos;
989
990 if (m_iBufferPos > 0)
991 --m_iBufferPos;
992 }
993 else
994 {
995 if (m_iBufferPos_Tail < m_iBufferPos)
996 m_iBufferPos = m_iBufferPos_Tail;
997
998 m_iBufferPos_Tail = -1;
999 }
1000
1001 UpdateAutoScroll();
1002 break;
1003 }
1004 case SDLK_RIGHT:
1005 {
1006 m_WantedX = 0.0f;
1007
1008 if (shiftKeyPressed || !SelectingText())
1009 {
1010 if (!shiftKeyPressed)
1011 m_iBufferPos_Tail = -1;
1012 else if (!SelectingText())
1013 m_iBufferPos_Tail = m_iBufferPos;
1014
1015 if (m_iBufferPos < static_cast<int>(m_Caption.length()))
1016 ++m_iBufferPos;
1017 }
1018 else
1019 {
1020 if (m_iBufferPos_Tail > m_iBufferPos)
1021 m_iBufferPos = m_iBufferPos_Tail;
1022
1023 m_iBufferPos_Tail = -1;
1024 }
1025
1026 UpdateAutoScroll();
1027 break;
1028 }
1029 /**
1030 * Conventions for Up/Down when text is selected:
1031 *
1032 * References:
1033 *
1034 * Visual Studio
1035 * Visual Studio has a very strange approach, down takes you below the
1036 * selection to the next row, and up to the one prior to the whole
1037 * selection. The weird part is that it is always aligned as the
1038 * 'pointer'. I decided this is to much work for something that is
1039 * a bit arbitrary
1040 *
1041 * Windows (eg. Notepad)
1042 * Just like with left/right, the selection is destroyed and it moves
1043 * just as if there never were a selection.
1044 *
1045 * I chose the Notepad convention even though I use the VS convention with
1046 * left/right.
1047 */
1048 case SDLK_UP:
1049 {
1050 if (!shiftKeyPressed)
1051 m_iBufferPos_Tail = -1;
1052 else if (!SelectingText())
1053 m_iBufferPos_Tail = m_iBufferPos;
1054
1055 std::list<SRow>::iterator current = m_CharacterPositions.begin();
1056 while (current != m_CharacterPositions.end())
1057 {
1058 if (m_iBufferPos >= current->m_ListStart &&
1059 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
1060 break;
1061
1062 ++current;
1063 }
1064
1065 float pos_x;
1066 if (m_iBufferPos - current->m_ListStart == 0)
1067 pos_x = 0.f;
1068 else
1069 pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1];
1070
1071 if (m_WantedX > pos_x)
1072 pos_x = m_WantedX;
1073
1074 // Now change row:
1075 if (current != m_CharacterPositions.begin())
1076 {
1077 --current;
1078
1079 // Find X-position:
1080 m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
1081 }
1082 // else we can't move up
1083
1084 UpdateAutoScroll();
1085 break;
1086 }
1087 case SDLK_DOWN:
1088 {
1089 if (!shiftKeyPressed)
1090 m_iBufferPos_Tail = -1;
1091 else if (!SelectingText())
1092 m_iBufferPos_Tail = m_iBufferPos;
1093
1094 std::list<SRow>::iterator current = m_CharacterPositions.begin();
1095 while (current != m_CharacterPositions.end())
1096 {
1097 if (m_iBufferPos >= current->m_ListStart &&
1098 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
1099 break;
1100
1101 ++current;
1102 }
1103
1104 float pos_x;
1105
1106 if (m_iBufferPos - current->m_ListStart == 0)
1107 pos_x = 0.f;
1108 else
1109 pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1];
1110
1111 if (m_WantedX > pos_x)
1112 pos_x = m_WantedX;
1113
1114 // Now change row:
1115 // Add first, so we can check if it's .end()
1116 ++current;
1117 if (current != m_CharacterPositions.end())
1118 {
1119 // Find X-position:
1120 m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
1121 }
1122 // else we can't move up
1123
1124 UpdateAutoScroll();
1125 break;
1126 }
1127 case SDLK_PAGEUP:
1128 {
1129 GetScrollBar(0).ScrollMinusPlenty();
1130 UpdateAutoScroll();
1131 break;
1132 }
1133 case SDLK_PAGEDOWN:
1134 {
1135 GetScrollBar(0).ScrollPlusPlenty();
1136 UpdateAutoScroll();
1137 break;
1138 }
1139 default:
1140 {
1141 break;
1142 }
1143 }
1144 }
1145
1146 InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
1147 {
1148 bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT];
1149
1150 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
1151
1152 if (hotkey == "paste")
1153 {
1154 if (m_Readonly)
1155 return IN_PASS;
1156
1157 m_WantedX = 0.0f;
1158
1159- wchar_t* text = sys_clipboard_get();
1160+ char* text = SDL_GetClipboardText();
1161 if (text)
1162 {
1163+ std::wstring wstring = wstring_from_utf8(text);
1164+ const wchar_t* wtext = wstring.c_str();
1165 if (SelectingText())
1166 DeleteCurSelection();
1167
1168 if (m_iBufferPos == static_cast<int>(m_Caption.length()))
1169- m_Caption += text;
1170+ m_Caption += wtext;
1171 else
1172 m_Caption =
1173- m_Caption.Left(m_iBufferPos) + text +
1174+ m_Caption.Left(m_iBufferPos) + wtext +
1175 m_Caption.Right(static_cast<long>(m_Caption.length()) - m_iBufferPos);
1176
1177 UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
1178
1179- m_iBufferPos += (int)wcslen(text);
1180+ m_iBufferPos += static_cast<int>(wcslen(wtext));
1181 UpdateAutoScroll();
1182 UpdateBufferPositionSetting();
1183
1184- sys_clipboard_free(text);
1185+ SDL_free(text);
1186
1187 SendEvent(GUIM_TEXTEDIT, "textedit");
1188 }
1189
1190 return IN_HANDLED;
1191 }
1192 else if (hotkey == "copy" || hotkey == "cut")
1193 {
1194 if (m_Readonly && hotkey == "cut")
1195 return IN_PASS;
1196
1197 m_WantedX = 0.0f;
1198
1199 if (SelectingText())
1200 {
1201 int virtualFrom;
1202 int virtualTo;
1203
1204 if (m_iBufferPos_Tail >= m_iBufferPos)
1205 {
1206 virtualFrom = m_iBufferPos;
1207 virtualTo = m_iBufferPos_Tail;
1208 }
1209 else
1210 {
1211 virtualFrom = m_iBufferPos_Tail;
1212 virtualTo = m_iBufferPos;
1213 }
1214
1215 CStrW text = m_Caption.Left(virtualTo).Right(virtualTo - virtualFrom);
1216
1217- sys_clipboard_set(&text[0]);
1218+ SDL_SetClipboardText(text.ToUTF8().c_str());
1219
1220 if (hotkey == "cut")
1221 {
1222 DeleteCurSelection();
1223 UpdateAutoScroll();
1224 SendEvent(GUIM_TEXTEDIT, "textedit");
1225 }
1226 }
1227
1228 return IN_HANDLED;
1229 }
1230 else if (hotkey == "text.delete.left")
1231 {
1232 if (m_Readonly)
1233 return IN_PASS;
1234
1235 m_WantedX = 0.0f;
1236
1237 if (SelectingText())
1238 DeleteCurSelection();
1239
1240 if (!m_Caption.empty() && m_iBufferPos != 0)
1241 {
1242 m_iBufferPos_Tail = m_iBufferPos;
1243 CStrW searchString = m_Caption.Left(m_iBufferPos);
1244
1245 // If we are starting in whitespace, adjust position until we get a non whitespace
1246 while (m_iBufferPos > 0)
1247 {
1248 if (!iswspace(searchString[m_iBufferPos - 1]))
1249 break;
1250
1251 m_iBufferPos--;
1252 }
1253
1254 // If we end up on a punctuation char we just delete it (treat punct like a word)
1255 if (iswpunct(searchString[m_iBufferPos - 1]))
1256 m_iBufferPos--;
1257 else
1258 {
1259 // Now we are on a non white space character, adjust position to char after next whitespace char is found
1260 while (m_iBufferPos > 0)
1261 {
1262 if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1]))
1263 break;
1264
1265 m_iBufferPos--;
1266 }
1267 }
1268
1269 UpdateBufferPositionSetting();
1270 DeleteCurSelection();
1271 SendEvent(GUIM_TEXTEDIT, "textedit");
1272 }
1273 UpdateAutoScroll();
1274 return IN_HANDLED;
1275 }
1276 else if (hotkey == "text.delete.right")
1277 {
1278 if (m_Readonly)
1279 return IN_PASS;
1280
1281 m_WantedX = 0.0f;
1282
1283 if (SelectingText())
1284 DeleteCurSelection();
1285
1286 if (!m_Caption.empty() && m_iBufferPos < static_cast<int>(m_Caption.length()))
1287 {
1288 // Delete the word to the right of the cursor
1289 m_iBufferPos_Tail = m_iBufferPos;
1290
1291 // Delete chars to the right unit we hit whitespace
1292 while (++m_iBufferPos < static_cast<int>(m_Caption.length()))
1293 {
1294 if (iswspace(m_Caption[m_iBufferPos]) || iswpunct(m_Caption[m_iBufferPos]))
1295 break;
1296 }
1297
1298 // Eliminate any whitespace behind the word we just deleted
1299 while (m_iBufferPos < static_cast<int>(m_Caption.length()))
1300 {
1301 if (!iswspace(m_Caption[m_iBufferPos]))
1302 break;
1303
1304 ++m_iBufferPos;
1305 }
1306 UpdateBufferPositionSetting();
1307 DeleteCurSelection();
1308 }
1309 UpdateAutoScroll();
1310 SendEvent(GUIM_TEXTEDIT, "textedit");
1311 return IN_HANDLED;
1312 }
1313 else if (hotkey == "text.move.left")
1314 {
1315 m_WantedX = 0.0f;
1316
1317 if (shiftKeyPressed || !SelectingText())
1318 {
1319 if (!shiftKeyPressed)
1320 m_iBufferPos_Tail = -1;
1321 else if (!SelectingText())
1322 m_iBufferPos_Tail = m_iBufferPos;
1323
1324 if (!m_Caption.empty() && m_iBufferPos != 0)
1325 {
1326 CStrW searchString = m_Caption.Left(m_iBufferPos);
1327
1328 // If we are starting in whitespace, adjust position until we get a non whitespace
1329 while (m_iBufferPos > 0)
1330 {
1331 if (!iswspace(searchString[m_iBufferPos - 1]))
1332 break;
1333
1334 m_iBufferPos--;
1335 }
1336
1337 // If we end up on a puctuation char we just select it (treat punct like a word)
1338 if (iswpunct(searchString[m_iBufferPos - 1]))
1339 m_iBufferPos--;
1340 else
1341 {
1342 // Now we are on a non white space character, adjust position to char after next whitespace char is found
1343 while (m_iBufferPos > 0)
1344 {
1345 if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1]))
1346 break;
1347
1348 m_iBufferPos--;
1349 }
1350 }
1351 }
1352 }
1353 else
1354 {
1355 if (m_iBufferPos_Tail < m_iBufferPos)
1356 m_iBufferPos = m_iBufferPos_Tail;
1357
1358 m_iBufferPos_Tail = -1;
1359 }
1360
1361 UpdateBufferPositionSetting();
1362 UpdateAutoScroll();
1363
1364 return IN_HANDLED;
1365 }
1366 else if (hotkey == "text.move.right")
1367 {
1368 m_WantedX = 0.0f;
1369
1370 if (shiftKeyPressed || !SelectingText())
1371 {
1372 if (!shiftKeyPressed)
1373 m_iBufferPos_Tail = -1;
1374 else if (!SelectingText())
1375 m_iBufferPos_Tail = m_iBufferPos;
1376
1377 if (!m_Caption.empty() && m_iBufferPos < static_cast<int>(m_Caption.length()))
1378 {
1379 // Select chars to the right until we hit whitespace
1380 while (++m_iBufferPos < static_cast<int>(m_Caption.length()))
1381 {
1382 if (iswspace(m_Caption[m_iBufferPos]) || iswpunct(m_Caption[m_iBufferPos]))
1383 break;
1384 }
1385
1386 // Also select any whitespace following the word we just selected
1387 while (m_iBufferPos < static_cast<int>(m_Caption.length()))
1388 {
1389 if (!iswspace(m_Caption[m_iBufferPos]))
1390 break;
1391
1392 ++m_iBufferPos;
1393 }
1394 }
1395 }
1396 else
1397 {
1398 if (m_iBufferPos_Tail > m_iBufferPos)
1399 m_iBufferPos = m_iBufferPos_Tail;
1400
1401 m_iBufferPos_Tail = -1;
1402 }
1403
1404 UpdateBufferPositionSetting();
1405 UpdateAutoScroll();
1406
1407 return IN_HANDLED;
1408 }
1409
1410 return IN_PASS;
1411 }
1412
1413 void CInput::ResetStates()
1414 {
1415 IGUIObject::ResetStates();
1416 IGUIScrollBarOwner::ResetStates();
1417 }
1418
1419 void CInput::HandleMessage(SGUIMessage& Message)
1420 {
1421 IGUIObject::HandleMessage(Message);
1422 IGUIScrollBarOwner::HandleMessage(Message);
1423
1424 switch (Message.type)
1425 {
1426 case GUIM_SETTINGS_UPDATED:
1427 {
1428 // Update scroll-bar
1429 // TODO Gee: (2004-09-01) Is this really updated each time it should?
1430 if (m_ScrollBar &&
1431 (Message.value == "size" ||
1432 Message.value == "z" ||
1433 Message.value == "absolute"))
1434 {
1435 GetScrollBar(0).SetX(m_CachedActualSize.right);
1436 GetScrollBar(0).SetY(m_CachedActualSize.top);
1437 GetScrollBar(0).SetZ(GetBufferedZ());
1438 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
1439 }
1440
1441 // Update scrollbar
1442 if (Message.value == "scrollbar_style")
1443 GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
1444
1445 if (Message.value == "buffer_position")
1446 {
1447 m_iBufferPos = m_BufferPosition;
1448 m_iBufferPos_Tail = -1; // position change resets selection
1449 }
1450
1451 if (Message.value == "size" ||
1452 Message.value == "z" ||
1453 Message.value == "font" ||
1454 Message.value == "absolute" ||
1455 Message.value == "caption" ||
1456 Message.value == "scrollbar" ||
1457 Message.value == "scrollbar_style")
1458 {
1459 UpdateText();
1460 }
1461
1462 if (Message.value == "multiline")
1463 {
1464 if (!m_MultiLine)
1465 GetScrollBar(0).SetLength(0.f);
1466 else
1467 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
1468
1469 UpdateText();
1470 }
1471
1472 UpdateAutoScroll();
1473
1474 break;
1475 }
1476 case GUIM_MOUSE_PRESS_LEFT:
1477 {
1478 // Check if we're selecting the scrollbar
1479 if (m_ScrollBar &&
1480 m_MultiLine &&
1481 GetScrollBar(0).GetStyle())
1482 {
1483 if (m_pGUI.GetMousePos().x > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width)
1484 break;
1485 }
1486
1487 if (m_ComposingText)
1488 break;
1489
1490 // Okay, this section is about pressing the mouse and
1491 // choosing where the point should be placed. For
1492 // instance, if we press between a and b, the point
1493 // should of course be placed accordingly. Other
1494 // special cases are handled like the input box norms.
1495 if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT])
1496 m_iBufferPos = GetMouseHoveringTextPosition();
1497 else
1498 m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
1499
1500 m_SelectingText = true;
1501
1502 UpdateAutoScroll();
1503
1504 // If we immediately release the button it will just be seen as a click
1505 // for the user though.
1506 break;
1507 }
1508 case GUIM_MOUSE_DBLCLICK_LEFT:
1509 {
1510 if (m_ComposingText)
1511 break;
1512
1513 if (m_Caption.empty())
1514 break;
1515
1516 m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
1517
1518 if (m_iBufferPos >= (int)m_Caption.length())
1519 m_iBufferPos = m_iBufferPos_Tail = m_Caption.length() - 1;
1520
1521 // See if we are clicking over whitespace
1522 if (iswspace(m_Caption[m_iBufferPos]))
1523 {
1524 // see if we are in a section of whitespace greater than one character
1525 if ((m_iBufferPos + 1 < (int) m_Caption.length() && iswspace(m_Caption[m_iBufferPos + 1])) ||
1526 (m_iBufferPos - 1 > 0 && iswspace(m_Caption[m_iBufferPos - 1])))
1527 {
1528 //
1529 // We are clicking in an area with more than one whitespace character
1530 // so we select both the word to the left and then the word to the right
1531 //
1532 // [1] First the left
1533 // skip the whitespace
1534 while (m_iBufferPos > 0)
1535 {
1536 if (!iswspace(m_Caption[m_iBufferPos - 1]))
1537 break;
1538
1539 m_iBufferPos--;
1540 }
1541 // now go until we hit white space or punctuation
1542 while (m_iBufferPos > 0)
1543 {
1544 if (iswspace(m_Caption[m_iBufferPos - 1]))
1545 break;
1546
1547 m_iBufferPos--;
1548
1549 if (iswpunct(m_Caption[m_iBufferPos]))
1550 break;
1551 }
1552
1553 // [2] Then the right
1554 // go right until we are not in whitespace
1555 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1556 {
1557 if (!iswspace(m_Caption[m_iBufferPos_Tail]))
1558 break;
1559 }
1560
1561 if (m_iBufferPos_Tail == static_cast<int>(m_Caption.length()))
1562 break;
1563
1564 // now go to the right until we hit whitespace or punctuation
1565 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1566 {
1567 if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail]))
1568 break;
1569 }
1570 }
1571 else
1572 {
1573 // single whitespace so select word to the right
1574 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1575 {
1576 if (!iswspace(m_Caption[m_iBufferPos_Tail]))
1577 break;
1578 }
1579
1580 if (m_iBufferPos_Tail == static_cast<int>(m_Caption.length()))
1581 break;
1582
1583 // Don't include the leading whitespace
1584 m_iBufferPos = m_iBufferPos_Tail;
1585
1586 // now go to the right until we hit whitespace or punctuation
1587 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1588 {
1589 if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail]))
1590 break;
1591 }
1592 }
1593 }
1594 else
1595 {
1596 // clicked on non-whitespace so select current word
1597 // go until we hit white space or punctuation
1598 while (m_iBufferPos > 0)
1599 {
1600 if (iswspace(m_Caption[m_iBufferPos - 1]))
1601 break;
1602
1603 m_iBufferPos--;
1604
1605 if (iswpunct(m_Caption[m_iBufferPos]))
1606 break;
1607 }
1608 // go to the right until we hit whitespace or punctuation
1609 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1610 if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail]))
1611 break;
1612 }
1613 UpdateAutoScroll();
1614 break;
1615 }
1616 case GUIM_MOUSE_RELEASE_LEFT:
1617 {
1618 if (m_SelectingText)
1619 m_SelectingText = false;
1620 break;
1621 }
1622 case GUIM_MOUSE_MOTION:
1623 {
1624 // If we just pressed down and started to move before releasing
1625 // this is one way of selecting larger portions of text.
1626 if (m_SelectingText)
1627 {
1628 // Actually, first we need to re-check that the mouse button is
1629 // really pressed (it can be released while outside the control.
1630 if (!g_mouse_buttons[SDL_BUTTON_LEFT])
1631 m_SelectingText = false;
1632 else
1633 m_iBufferPos = GetMouseHoveringTextPosition();
1634 UpdateAutoScroll();
1635 }
1636 break;
1637 }
1638 case GUIM_LOAD:
1639 {
1640 GetScrollBar(0).SetX(m_CachedActualSize.right);
1641 GetScrollBar(0).SetY(m_CachedActualSize.top);
1642 GetScrollBar(0).SetZ(GetBufferedZ());
1643 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
1644 GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
1645
1646 UpdateText();
1647 UpdateAutoScroll();
1648
1649 break;
1650 }
1651 case GUIM_GOT_FOCUS:
1652 {
1653 m_iBufferPos = 0;
1654 m_PrevTime = 0.0;
1655 m_CursorVisState = false;
1656
1657 // Tell the IME where to draw the candidate list
1658 SDL_Rect rect;
1659 rect.h = m_CachedActualSize.GetSize().cy;
1660 rect.w = m_CachedActualSize.GetSize().cx;
1661 rect.x = m_CachedActualSize.TopLeft().x;
1662 rect.y = m_CachedActualSize.TopLeft().y;
1663 SDL_SetTextInputRect(&rect);
1664 SDL_StartTextInput();
1665 break;
1666 }
1667 case GUIM_LOST_FOCUS:
1668 {
1669 if (m_ComposingText)
1670 {
1671 // Simulate a final text editing event to clear the composition
1672 SDL_Event_ evt;
1673 evt.ev.type = SDL_TEXTEDITING;
1674 evt.ev.edit.length = 0;
1675 evt.ev.edit.start = 0;
1676 evt.ev.edit.text[0] = 0;
1677 ManuallyHandleEvent(&evt);
1678 }
1679 SDL_StopTextInput();
1680
1681 m_iBufferPos = -1;
1682 m_iBufferPos_Tail = -1;
1683 break;
1684 }
1685 default:
1686 {
1687 break;
1688 }
1689 }
1690 UpdateBufferPositionSetting();
1691 }
1692
1693 void CInput::UpdateCachedSize()
1694 {
1695 // If an ancestor's size changed, this will let us intercept the change and
1696 // update our scrollbar positions
1697
1698 IGUIObject::UpdateCachedSize();
1699
1700 if (m_ScrollBar)
1701 {
1702 GetScrollBar(0).SetX(m_CachedActualSize.right);
1703 GetScrollBar(0).SetY(m_CachedActualSize.top);
1704 GetScrollBar(0).SetZ(GetBufferedZ());
1705 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
1706 }
1707 }
1708
1709 void CInput::Draw()
1710 {
1711 float bz = GetBufferedZ();
1712
1713 if (m_CursorBlinkRate > 0.0)
1714 {
1715 // check if the cursor visibility state needs to be changed
1716 double currTime = timer_Time();
1717 if (currTime - m_PrevTime >= m_CursorBlinkRate)
1718 {
1719 m_CursorVisState = !m_CursorVisState;
1720 m_PrevTime = currTime;
1721 }
1722 }
1723 else
1724 // should always be visible
1725 m_CursorVisState = true;
1726
1727 // First call draw on ScrollBarOwner
1728 if (m_ScrollBar && m_MultiLine)
1729 IGUIScrollBarOwner::Draw();
1730
1731 CStrIntern font_name(m_Font.ToUTF8());
1732
1733 wchar_t mask_char = L'*';
1734 if (m_Mask && m_MaskChar.length() > 0)
1735 mask_char = m_MaskChar[0];
1736
1737 m_pGUI.DrawSprite(m_Sprite, m_CellID, bz, m_CachedActualSize);
1738
1739 float scroll = 0.f;
1740 if (m_ScrollBar && m_MultiLine)
1741 scroll = GetScrollBar(0).GetPos();
1742
1743 CFontMetrics font(font_name);
1744
1745 // We'll have to setup clipping manually, since we're doing the rendering manually.
1746 CRect cliparea(m_CachedActualSize);
1747
1748 // First we'll figure out the clipping area, which is the cached actual size
1749 // substracted by an optional scrollbar
1750 if (m_ScrollBar)
1751 {
1752 scroll = GetScrollBar(0).GetPos();
1753
1754 // substract scrollbar from cliparea
1755 if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
1756 cliparea.right <= GetScrollBar(0).GetOuterRect().right)
1757 cliparea.right = GetScrollBar(0).GetOuterRect().left;
1758
1759 if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
1760 cliparea.left < GetScrollBar(0).GetOuterRect().right)
1761 cliparea.left = GetScrollBar(0).GetOuterRect().right;
1762 }
1763
1764 if (cliparea != CRect())
1765 {
1766 glEnable(GL_SCISSOR_TEST);
1767 glScissor(
1768 cliparea.left * g_GuiScale,
1769 g_yres - cliparea.bottom * g_GuiScale,
1770 cliparea.GetWidth() * g_GuiScale,
1771 cliparea.GetHeight() * g_GuiScale);
1772 }
1773
1774 // These are useful later.
1775 int VirtualFrom, VirtualTo;
1776
1777 if (m_iBufferPos_Tail >= m_iBufferPos)
1778 {
1779 VirtualFrom = m_iBufferPos;
1780 VirtualTo = m_iBufferPos_Tail;
1781 }
1782 else
1783 {
1784 VirtualFrom = m_iBufferPos_Tail;
1785 VirtualTo = m_iBufferPos;
1786 }
1787
1788 // Get the height of this font.
1789 float h = (float)font.GetHeight();
1790 float ls = (float)font.GetLineSpacing();
1791
1792 CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
1793
1794 CTextRenderer textRenderer(tech->GetShader());
1795 textRenderer.Font(font_name);
1796
1797 // Set the Z to somewhat more, so we can draw a selected area between the
1798 // the control and the text.
1799 textRenderer.Translate(
1800 (float)(int)(m_CachedActualSize.left) + m_BufferZone,
1801 (float)(int)(m_CachedActualSize.top+h) + m_BufferZone,
1802 bz+0.1f);
1803
1804 // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE
1805 // (sort of like a | which is aligned to the left of most characters)
1806
1807 float buffered_y = -scroll + m_BufferZone;
1808
1809 // When selecting larger areas, we need to draw a rectangle box
1810 // around it, and this is to keep track of where the box
1811 // started, because we need to follow the iteration until we
1812 // reach the end, before we can actually draw it.
1813 bool drawing_box = false;
1814 float box_x = 0.f;
1815
1816 float x_pointer = 0.f;
1817
1818 // If we have a selecting box (i.e. when you have selected letters, not just when
1819 // the pointer is between two letters) we need to process all letters once
1820 // before we do it the second time and render all the text. We can't do it
1821 // in the same loop because text will have been drawn, so it will disappear when
1822 // drawn behind the text that has already been drawn. Confusing, well it's necessary
1823 // (I think).
1824
1825 if (SelectingText())
1826 {
1827 // Now m_iBufferPos_Tail can be of both sides of m_iBufferPos,
1828 // just like you can select from right to left, as you can
1829 // left to right. Is there a difference? Yes, the pointer
1830 // be placed accordingly, so that if you select shift and
1831 // expand this selection, it will expand on appropriate side.
1832 // Anyway, since the drawing procedure needs "To" to be
1833 // greater than from, we need virtual values that might switch
1834 // place.
1835
1836 int VirtualFrom, VirtualTo;
1837
1838 if (m_iBufferPos_Tail >= m_iBufferPos)
1839 {
1840 VirtualFrom = m_iBufferPos;
1841 VirtualTo = m_iBufferPos_Tail;
1842 }
1843 else
1844 {
1845 VirtualFrom = m_iBufferPos_Tail;
1846 VirtualTo = m_iBufferPos;
1847 }
1848
1849
1850 bool done = false;
1851 for (std::list<SRow>::const_iterator it = m_CharacterPositions.begin();
1852 it != m_CharacterPositions.end();
1853 ++it, buffered_y += ls, x_pointer = 0.f)
1854 {
1855 if (m_MultiLine && buffered_y > m_CachedActualSize.GetHeight())
1856 break;
1857
1858 // We might as well use 'i' here to iterate, because we need it
1859 // (often compared against ints, so don't make it size_t)
1860 for (int i = 0; i < (int)it->m_ListOfX.size()+2; ++i)
1861 {
1862 if (it->m_ListStart + i == VirtualFrom)
1863 {
1864 // we won't actually draw it now, because we don't
1865 // know the width of each glyph to that position.
1866 // we need to go along with the iteration, and
1867 // make a mark where the box started:
1868 drawing_box = true; // will turn false when finally rendered.
1869
1870 // Get current x position
1871 box_x = x_pointer;
1872 }
1873
1874 const bool at_end = (i == (int)it->m_ListOfX.size()+1);
1875
1876 if (drawing_box && (it->m_ListStart + i == VirtualTo || at_end))
1877 {
1878 // Depending on if it's just a row change, or if it's
1879 // the end of the select box, do slightly different things.
1880 if (at_end)
1881 {
1882 if (it->m_ListStart + i != VirtualFrom)
1883 // and actually add a white space! yes, this is done in any common input
1884 x_pointer += font.GetCharacterWidth(L' ');
1885 }
1886 else
1887 {
1888 drawing_box = false;
1889 done = true;
1890 }
1891
1892 CRect rect;
1893 // Set 'rect' depending on if it's a multiline control, or a one-line control
1894 if (m_MultiLine)
1895 {
1896 rect = CRect(
1897 m_CachedActualSize.left + box_x + m_BufferZone,
1898 m_CachedActualSize.top + buffered_y + (h - ls) / 2,
1899 m_CachedActualSize.left + x_pointer + m_BufferZone,
1900 m_CachedActualSize.top + buffered_y + (h + ls) / 2);
1901
1902 if (rect.bottom < m_CachedActualSize.top)
1903 continue;
1904
1905 if (rect.top < m_CachedActualSize.top)
1906 rect.top = m_CachedActualSize.top;
1907
1908 if (rect.bottom > m_CachedActualSize.bottom)
1909 rect.bottom = m_CachedActualSize.bottom;
1910 }
1911 else // if one-line
1912 {
1913 rect = CRect(
1914 m_CachedActualSize.left + box_x + m_BufferZone - m_HorizontalScroll,
1915 m_CachedActualSize.top + buffered_y + (h - ls) / 2,
1916 m_CachedActualSize.left + x_pointer + m_BufferZone - m_HorizontalScroll,
1917 m_CachedActualSize.top + buffered_y + (h + ls) / 2);
1918
1919 if (rect.left < m_CachedActualSize.left)
1920 rect.left = m_CachedActualSize.left;
1921
1922 if (rect.right > m_CachedActualSize.right)
1923 rect.right = m_CachedActualSize.right;
1924 }
1925
1926 m_pGUI.DrawSprite(m_SpriteSelectArea, m_CellID, bz + 0.05f, rect);
1927 }
1928
1929 if (i < (int)it->m_ListOfX.size())
1930 {
1931 if (!m_Mask)
1932 x_pointer += font.GetCharacterWidth(m_Caption[it->m_ListStart + i]);
1933 else
1934 x_pointer += font.GetCharacterWidth(mask_char);
1935 }
1936 }
1937
1938 if (done)
1939 break;
1940
1941 // If we're about to draw a box, and all of a sudden changes
1942 // line, we need to draw that line's box, and then reset
1943 // the box drawing to the beginning of the new line.
1944 if (drawing_box)
1945 box_x = 0.f;
1946 }
1947 }
1948
1949 // Reset some from previous run
1950 buffered_y = -scroll;
1951
1952 // Setup initial color (then it might change and change back, when drawing selected area)
1953 textRenderer.Color(m_TextColor);
1954
1955 tech->BeginPass();
1956
1957 bool using_selected_color = false;
1958
1959 for (std::list<SRow>::const_iterator it = m_CharacterPositions.begin();
1960 it != m_CharacterPositions.end();
1961 ++it, buffered_y += ls)
1962 {
1963 if (buffered_y + m_BufferZone >= -ls || !m_MultiLine)
1964 {
1965 if (m_MultiLine && buffered_y + m_BufferZone > m_CachedActualSize.GetHeight())
1966 break;
1967
1968 CMatrix3D savedTransform = textRenderer.GetTransform();
1969
1970 // Text must always be drawn in integer values. So we have to convert scroll
1971 if (m_MultiLine)
1972 textRenderer.Translate(0.f, -(float)(int)scroll, 0.f);
1973 else
1974 textRenderer.Translate(-(float)(int)m_HorizontalScroll, 0.f, 0.f);
1975
1976 // We might as well use 'i' here, because we need it
1977 // (often compared against ints, so don't make it size_t)
1978 for (int i = 0; i < (int)it->m_ListOfX.size()+1; ++i)
1979 {
1980 if (!m_MultiLine && i < (int)it->m_ListOfX.size())
1981 {
1982 if (it->m_ListOfX[i] - m_HorizontalScroll < -m_BufferZone)
1983 {
1984 // We still need to translate the OpenGL matrix
1985 if (i == 0)
1986 textRenderer.Translate(it->m_ListOfX[i], 0.f, 0.f);
1987 else
1988 textRenderer.Translate(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f);
1989
1990 continue;
1991 }
1992 }
1993
1994 // End of selected area, change back color
1995 if (SelectingText() && it->m_ListStart + i == VirtualTo)
1996 {
1997 using_selected_color = false;
1998 textRenderer.Color(m_TextColor);
1999 }
2000
2001 // selecting only one, then we need only to draw a cursor.
2002 if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos && m_CursorVisState)
2003 textRenderer.Put(0.0f, 0.0f, L"_");
2004
2005 // Drawing selected area
2006 if (SelectingText() &&
2007 it->m_ListStart + i >= VirtualFrom &&
2008 it->m_ListStart + i < VirtualTo &&
2009 !using_selected_color)
2010 {
2011 using_selected_color = true;
2012 textRenderer.Color(m_TextColorSelected);
2013 }
2014
2015 if (i != (int)it->m_ListOfX.size())
2016 {
2017 if (!m_Mask)
2018 textRenderer.PrintfAdvance(L"%lc", m_Caption[it->m_ListStart + i]);
2019 else
2020 textRenderer.PrintfAdvance(L"%lc", mask_char);
2021 }
2022
2023 // check it's now outside a one-liner, then we'll break
2024 if (!m_MultiLine && i < (int)it->m_ListOfX.size() &&
2025 it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth() - m_BufferZone)
2026 break;
2027 }
2028
2029 if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos)
2030 {
2031 textRenderer.Color(m_TextColor);
2032 if (m_CursorVisState)
2033 textRenderer.PutAdvance(L"_");
2034
2035 if (using_selected_color)
2036 textRenderer.Color(m_TextColorSelected);
2037 }
2038
2039 textRenderer.SetTransform(savedTransform);
2040 }
2041
2042 textRenderer.Translate(0.f, ls, 0.f);
2043 }
2044
2045 textRenderer.Render();
2046
2047 if (cliparea != CRect())
2048 glDisable(GL_SCISSOR_TEST);
2049
2050 tech->EndPass();
2051 }
2052
2053 void CInput::UpdateText(int from, int to_before, int to_after)
2054 {
2055 CStrIntern font_name(m_Font.ToUTF8());
2056
2057 wchar_t mask_char = L'*';
2058 if (m_Mask && m_MaskChar.length() > 0)
2059 mask_char = m_MaskChar[0];
2060
2061 // Ensure positions are valid after caption changes
2062 m_iBufferPos = std::min(m_iBufferPos, static_cast<int>(m_Caption.size()));
2063 m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, static_cast<int>(m_Caption.size()));
2064 UpdateBufferPositionSetting();
2065
2066 if (font_name.empty())
2067 {
2068 // Destroy everything stored, there's no font, so there can be no data.
2069 m_CharacterPositions.clear();
2070 return;
2071 }
2072
2073 SRow row;
2074 row.m_ListStart = 0;
2075
2076 int to = 0; // make sure it's initialized
2077
2078 if (to_before == -1)
2079 to = static_cast<int>(m_Caption.length());
2080
2081 CFontMetrics font(font_name);
2082
2083 std::list<SRow>::iterator current_line;
2084
2085 // Used to ... TODO
2086 int check_point_row_start = -1;
2087 int check_point_row_end = -1;
2088
2089 // Reset
2090 if (from == 0 && to_before == -1)
2091 {
2092 m_CharacterPositions.clear();
2093 current_line = m_CharacterPositions.begin();
2094 }
2095 else
2096 {
2097 ENSURE(to_before != -1);
2098
2099 std::list<SRow>::iterator destroy_row_from;
2100 std::list<SRow>::iterator destroy_row_to;
2101 // Used to check if the above has been set to anything,
2102 // previously a comparison like:
2103 // destroy_row_from == std::list<SRow>::iterator()
2104 // ... was used, but it didn't work with GCC.
2105 bool destroy_row_from_used = false;
2106 bool destroy_row_to_used = false;
2107
2108 // Iterate, and remove everything between 'from' and 'to_before'
2109 // actually remove the entire lines they are on, it'll all have
2110 // to be redone. And when going along, we'll delete a row at a time
2111 // when continuing to see how much more after 'to' we need to remake.
2112 int i = 0;
2113 for (std::list<SRow>::iterator it = m_CharacterPositions.begin();
2114 it != m_CharacterPositions.end();
2115 ++it, ++i)
2116 {
2117 if (!destroy_row_from_used && it->m_ListStart > from)
2118 {
2119 // Destroy the previous line, and all to 'to_before'
2120 destroy_row_from = it;
2121 --destroy_row_from;
2122
2123 destroy_row_from_used = true;
2124
2125 // For the rare case that we might remove characters to a word
2126 // so that it suddenly fits on the previous row,
2127 // we need to by standards re-do the whole previous line too
2128 // (if one exists)
2129 if (destroy_row_from != m_CharacterPositions.begin())
2130 --destroy_row_from;
2131 }
2132
2133 if (!destroy_row_to_used && it->m_ListStart > to_before)
2134 {
2135 destroy_row_to = it;
2136 destroy_row_to_used = true;
2137
2138 // If it isn't the last row, we'll add another row to delete,
2139 // just so we can see if the last restorted line is
2140 // identical to what it was before. If it isn't, then we'll
2141 // have to continue.
2142 // 'check_point_row_start' is where we store how the that
2143 // line looked.
2144 if (destroy_row_to != m_CharacterPositions.end())
2145 {
2146 check_point_row_start = destroy_row_to->m_ListStart;
2147 check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
2148 if (destroy_row_to->m_ListOfX.empty())
2149 ++check_point_row_end;
2150 }
2151
2152 ++destroy_row_to;
2153 break;
2154 }
2155 }
2156
2157 if (!destroy_row_from_used)
2158 {
2159 destroy_row_from = m_CharacterPositions.end();
2160 --destroy_row_from;
2161
2162 // As usual, let's destroy another row back
2163 if (destroy_row_from != m_CharacterPositions.begin())
2164 --destroy_row_from;
2165
2166 current_line = destroy_row_from;
2167 }
2168
2169 if (!destroy_row_to_used)
2170 {
2171 destroy_row_to = m_CharacterPositions.end();
2172 check_point_row_start = -1;
2173 }
2174
2175 // set 'from' to the row we'll destroy from
2176 // and 'to' to the row we'll destroy to
2177 from = destroy_row_from->m_ListStart;
2178
2179 if (destroy_row_to != m_CharacterPositions.end())
2180 to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to.
2181 else
2182 to = static_cast<int>(m_Caption.length());
2183
2184
2185 // Setup the first row
2186 row.m_ListStart = destroy_row_from->m_ListStart;
2187
2188 std::list<SRow>::iterator temp_it = destroy_row_to;
2189 --temp_it;
2190
2191 current_line = m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
2192
2193 // If there has been a change in number of characters
2194 // we need to change all m_ListStart that comes after
2195 // the interval we just destroyed. We'll change all
2196 // values with the delta change of the string length.
2197 int delta = to_after - to_before;
2198 if (delta != 0)
2199 {
2200 for (std::list<SRow>::iterator it = current_line;
2201 it != m_CharacterPositions.end();
2202 ++it)
2203 it->m_ListStart += delta;
2204
2205 // Update our check point too!
2206 check_point_row_start += delta;
2207 check_point_row_end += delta;
2208
2209 if (to != static_cast<int>(m_Caption.length()))
2210 to += delta;
2211 }
2212 }
2213
2214 int last_word_started = from;
2215 float x_pos = 0.f;
2216
2217 //if (to_before != -1)
2218 // return;
2219
2220 for (int i = from; i < to; ++i)
2221 {
2222 if (m_Caption[i] == L'\n' && m_MultiLine)
2223 {
2224 if (i == to-1 && to != static_cast<int>(m_Caption.length()))
2225 break; // it will be added outside
2226
2227 current_line = m_CharacterPositions.insert(current_line, row);
2228 ++current_line;
2229
2230 // Setup the next row:
2231 row.m_ListOfX.clear();
2232 row.m_ListStart = i+1;
2233 x_pos = 0.f;
2234 }
2235 else
2236 {
2237 if (m_Caption[i] == L' '/* || TODO Gee (2004-10-13): the '-' disappears, fix.
2238 m_Caption[i] == L'-'*/)
2239 last_word_started = i+1;
2240
2241 if (!m_Mask)
2242 x_pos += font.GetCharacterWidth(m_Caption[i]);
2243 else
2244 x_pos += font.GetCharacterWidth(mask_char);
2245
2246 if (x_pos >= GetTextAreaWidth() && m_MultiLine)
2247 {
2248 // The following decides whether it will word-wrap a word,
2249 // or if it's only one word on the line, where it has to
2250 // break the word apart.
2251 if (last_word_started == row.m_ListStart)
2252 {
2253 last_word_started = i;
2254 row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started));
2255 //row.m_ListOfX.push_back(x_pos);
2256 //continue;
2257 }
2258 else
2259 {
2260 // regular word-wrap
2261 row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1));
2262 }
2263
2264 // Now, create a new line:
2265 // notice: when we enter a newline, you can stand with the cursor
2266 // both before and after that character, being on different
2267 // rows. With automatic word-wrapping, that is not possible. Which
2268 // is intuitively correct.
2269
2270 current_line = m_CharacterPositions.insert(current_line, row);
2271 ++current_line;
2272
2273 // Setup the next row:
2274 row.m_ListOfX.clear();
2275 row.m_ListStart = last_word_started;
2276
2277 i = last_word_started-1;
2278
2279 x_pos = 0.f;
2280 }
2281 else
2282 // Get width of this character:
2283 row.m_ListOfX.push_back(x_pos);
2284 }
2285
2286 // Check if it's the last iteration, and we're not revising the whole string
2287 // because in that case, more word-wrapping might be needed.
2288 // also check if the current line isn't the end
2289 if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end())
2290 {
2291 // check all rows and see if any existing
2292 if (row.m_ListStart != check_point_row_start)
2293 {
2294 std::list<SRow>::iterator destroy_row_from;
2295 std::list<SRow>::iterator destroy_row_to;
2296 // Are used to check if the above has been set to anything,
2297 // previously a comparison like:
2298 // destroy_row_from == std::list<SRow>::iterator()
2299 // was used, but it didn't work with GCC.
2300 bool destroy_row_from_used = false;
2301 bool destroy_row_to_used = false;
2302
2303 // Iterate, and remove everything between 'from' and 'to_before'
2304 // actually remove the entire lines they are on, it'll all have
2305 // to be redone. And when going along, we'll delete a row at a time
2306 // when continuing to see how much more after 'to' we need to remake.
2307 int i = 0;
2308 for (std::list<SRow>::iterator it = m_CharacterPositions.begin();
2309 it != m_CharacterPositions.end();
2310 ++it, ++i)
2311 {
2312 if (!destroy_row_from_used && it->m_ListStart > check_point_row_start)
2313 {
2314 // Destroy the previous line, and all to 'to_before'
2315 //if (i >= 2)
2316 // destroy_row_from = it-2;
2317 //else
2318 // destroy_row_from = it-1;
2319 destroy_row_from = it;
2320 destroy_row_from_used = true;
2321 //--destroy_row_from;
2322 }
2323
2324 if (!destroy_row_to_used && it->m_ListStart > check_point_row_end)
2325 {
2326 destroy_row_to = it;
2327 destroy_row_to_used = true;
2328
2329 // If it isn't the last row, we'll add another row to delete,
2330 // just so we can see if the last restorted line is
2331 // identical to what it was before. If it isn't, then we'll
2332 // have to continue.
2333 // 'check_point_row_start' is where we store how the that
2334 // line looked.
2335 if (destroy_row_to != m_CharacterPositions.end())
2336 {
2337 check_point_row_start = destroy_row_to->m_ListStart;
2338 check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
2339 if (destroy_row_to->m_ListOfX.empty())
2340 ++check_point_row_end;
2341 }
2342 else
2343 check_point_row_start = check_point_row_end = -1;
2344
2345 ++destroy_row_to;
2346 break;
2347 }
2348 }
2349
2350 if (!destroy_row_from_used)
2351 {
2352 destroy_row_from = m_CharacterPositions.end();
2353 --destroy_row_from;
2354
2355 current_line = destroy_row_from;
2356 }
2357
2358 if (!destroy_row_to_used)
2359 {
2360 destroy_row_to = m_CharacterPositions.end();
2361 check_point_row_start = check_point_row_end = -1;
2362 }
2363
2364 if (destroy_row_to != m_CharacterPositions.end())
2365 to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to.
2366 else
2367 to = static_cast<int>(m_Caption.length());
2368
2369
2370 // Set current line, new rows will be added before current_line, so
2371 // we'll choose the destroy_row_to, because it won't be deleted
2372 // in the coming erase.
2373 current_line = destroy_row_to;
2374
2375 m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
2376 }
2377 // else, the for loop will end naturally.
2378 }
2379 }
2380 // This is kind of special, when we renew a some lines, then the last
2381 // one will sometimes end with a space (' '), that really should
2382 // be omitted when word-wrapping. So we'll check if the last row
2383 // we'll add has got the same value as the next row.
2384 if (current_line != m_CharacterPositions.end())
2385 {
2386 if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart)
2387 row.m_ListOfX.resize(row.m_ListOfX.size()-1);
2388 }
2389
2390 // add the final row (even if empty)
2391 m_CharacterPositions.insert(current_line, row);
2392
2393 if (m_ScrollBar)
2394 {
2395 GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + m_BufferZone * 2.f);
2396 GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight());
2397 }
2398 }
2399
2400 int CInput::GetMouseHoveringTextPosition() const
2401 {
2402 if (m_CharacterPositions.empty())
2403 return 0;
2404
2405 // Return position
2406 int retPosition;
2407
2408 std::list<SRow>::const_iterator current = m_CharacterPositions.begin();
2409
2410 CPos mouse = m_pGUI.GetMousePos();
2411
2412 if (m_MultiLine)
2413 {
2414 float scroll = 0.f;
2415 if (m_ScrollBar)
2416 scroll = GetScrollBarPos(0);
2417
2418 // Now get the height of the font.
2419 // TODO: Get the real font
2420 CFontMetrics font(CStrIntern(m_Font.ToUTF8()));
2421 float spacing = (float)font.GetLineSpacing();
2422
2423 // Change mouse position relative to text.
2424 mouse -= m_CachedActualSize.TopLeft();
2425 mouse.x -= m_BufferZone;
2426 mouse.y += scroll - m_BufferZone;
2427
2428 int row = (int)((mouse.y) / spacing);
2429
2430 if (row < 0)
2431 row = 0;
2432
2433 if (row > (int)m_CharacterPositions.size()-1)
2434 row = (int)m_CharacterPositions.size()-1;
2435
2436 // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to
2437 // be able to get the specific element here. This is hopefully a temporary hack.
2438
2439 for (int i = 0; i < row; ++i)
2440 ++current;
2441 }
2442 else
2443 {
2444 // current is already set to begin,
2445 // but we'll change the mouse.x to fit our horizontal scrolling
2446 mouse -= m_CachedActualSize.TopLeft();
2447 mouse.x -= m_BufferZone - m_HorizontalScroll;
2448 // mouse.y is moot
2449 }
2450
2451 retPosition = current->m_ListStart;
2452
2453 // Okay, now loop through the glyphs to find the appropriate X position
2454 float dummy;
2455 retPosition += GetXTextPosition(current, mouse.x, dummy);
2456
2457 return retPosition;
2458 }
2459
2460 // Does not process horizontal scrolling, 'x' must be modified before inputted.
2461 int CInput::GetXTextPosition(const std::list<SRow>::const_iterator& current, const float& x, float& wanted) const
2462 {
2463 int ret = 0;
2464 float previous = 0.f;
2465 int i = 0;
2466
2467 for (std::vector<float>::const_iterator it = current->m_ListOfX.begin();
2468 it != current->m_ListOfX.end();
2469 ++it, ++i)
2470 {
2471 if (*it >= x)
2472 {
2473 if (x - previous >= *it - x)
2474 ret += i+1;
2475 else
2476 ret += i;
2477
2478 break;
2479 }
2480 previous = *it;
2481 }
2482 // If a position wasn't found, we will assume the last
2483 // character of that line.
2484 if (i == (int)current->m_ListOfX.size())
2485 {
2486 ret += i;
2487 wanted = x;
2488 }
2489 else
2490 wanted = 0.f;
2491
2492 return ret;
2493 }
2494
2495 void CInput::DeleteCurSelection()
2496 {
2497 int virtualFrom;
2498 int virtualTo;
2499
2500 if (m_iBufferPos_Tail >= m_iBufferPos)
2501 {
2502 virtualFrom = m_iBufferPos;
2503 virtualTo = m_iBufferPos_Tail;
2504 }
2505 else
2506 {
2507 virtualFrom = m_iBufferPos_Tail;
2508 virtualTo = m_iBufferPos;
2509 }
2510
2511 m_Caption =
2512 m_Caption.Left(virtualFrom) +
2513 m_Caption.Right(static_cast<long>(m_Caption.length()) - virtualTo);
2514
2515 UpdateText(virtualFrom, virtualTo, virtualFrom);
2516
2517 // Remove selection
2518 m_iBufferPos_Tail = -1;
2519 m_iBufferPos = virtualFrom;
2520 UpdateBufferPositionSetting();
2521 }
2522
2523 bool CInput::SelectingText() const
2524 {
2525 return m_iBufferPos_Tail != -1 &&
2526 m_iBufferPos_Tail != m_iBufferPos;
2527 }
2528
2529 float CInput::GetTextAreaWidth()
2530 {
2531 if (m_ScrollBar && GetScrollBar(0).GetStyle())
2532 return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f - GetScrollBar(0).GetStyle()->m_Width;
2533
2534 return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f;
2535 }
2536
2537 void CInput::UpdateAutoScroll()
2538 {
2539 // Autoscrolling up and down
2540 if (m_MultiLine)
2541 {
2542 if (!m_ScrollBar)
2543 return;
2544
2545 const float scroll = GetScrollBar(0).GetPos();
2546
2547 // Now get the height of the font.
2548 // TODO: Get the real font
2549 CFontMetrics font(CStrIntern(m_Font.ToUTF8()));
2550 float spacing = (float)font.GetLineSpacing();
2551 //float height = font.GetHeight();
2552
2553 // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to
2554 // be able to get the specific element here. This is hopefully a temporary hack.
2555
2556 std::list<SRow>::iterator current = m_CharacterPositions.begin();
2557 int row = 0;
2558 while (current != m_CharacterPositions.end())
2559 {
2560 if (m_iBufferPos >= current->m_ListStart &&
2561 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
2562 break;
2563
2564 ++current;
2565 ++row;
2566 }
2567
2568 // If scrolling down
2569 if (-scroll + static_cast<float>(row + 1) * spacing + m_BufferZone * 2.f > m_CachedActualSize.GetHeight())
2570 {
2571 // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge.
2572 GetScrollBar(0).SetPos(static_cast<float>(row + 1) * spacing - m_CachedActualSize.GetHeight() + m_BufferZone * 2.f);
2573 }
2574 // If scrolling up
2575 else if (-scroll + (float)row * spacing < 0.f)
2576 {
2577 // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge.
2578 GetScrollBar(0).SetPos((float)row * spacing);
2579 }
2580 }
2581 else // autoscrolling left and right
2582 {
2583 // Get X position of position:
2584 if (m_CharacterPositions.empty())
2585 return;
2586
2587 float x_position = 0.f;
2588 float x_total = 0.f;
2589 if (!m_CharacterPositions.begin()->m_ListOfX.empty())
2590 {
2591
2592 // Get position of m_iBufferPos
2593 if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos &&
2594 m_iBufferPos > 0)
2595 x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1];
2596
2597 // Get complete length:
2598 x_total = m_CharacterPositions.begin()->m_ListOfX[m_CharacterPositions.begin()->m_ListOfX.size()-1];
2599 }
2600
2601 // Check if outside to the right
2602 if (x_position - m_HorizontalScroll + m_BufferZone * 2.f > m_CachedActualSize.GetWidth())
2603 m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f;
2604
2605 // Check if outside to the left
2606 if (x_position - m_HorizontalScroll < 0.f)
2607 m_HorizontalScroll = x_position;
2608
2609 // Check if the text doesn't even fill up to the right edge even though scrolling is done.
2610 if (m_HorizontalScroll != 0.f &&
2611 x_total - m_HorizontalScroll + m_BufferZone * 2.f < m_CachedActualSize.GetWidth())
2612 m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f;
2613
2614 // Now this is the fail-safe, if x_total isn't even the length of the control,
2615 // remove all scrolling
2616 if (x_total + m_BufferZone * 2.f < m_CachedActualSize.GetWidth())
2617 m_HorizontalScroll = 0.f;
2618 }
2619 }
2620Index: source/lib/lib_api.h
2621===================================================================
2622--- source/lib/lib_api.h (revision 23275)
2623+++ source/lib/lib_api.h (working copy)
2624@@ -1,56 +1,51 @@
2625 /* Copyright (C) 2010 Wildfire Games.
2626 *
2627 * Permission is hereby granted, free of charge, to any person obtaining
2628 * a copy of this software and associated documentation files (the
2629 * "Software"), to deal in the Software without restriction, including
2630 * without limitation the rights to use, copy, modify, merge, publish,
2631 * distribute, sublicense, and/or sell copies of the Software, and to
2632 * permit persons to whom the Software is furnished to do so, subject to
2633 * the following conditions:
2634 *
2635 * The above copyright notice and this permission notice shall be included
2636 * in all copies or substantial portions of the Software.
2637 *
2638 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2639 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2640 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2641 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2642 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2643 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2644 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2645 */
2646
2647 #ifndef INCLUDED_LIB_API
2648 #define INCLUDED_LIB_API
2649
2650 #include "lib/sysdep/compiler.h"
2651
2652 // note: EXTERN_C cannot be used because shared_ptr is often returned
2653 // by value, which requires C++ linkage.
2654
2655 #ifdef LIB_STATIC_LINK
2656 # define LIB_API
2657 #else
2658 # if MSC_VERSION
2659 # ifdef LIB_BUILD
2660 # define LIB_API __declspec(dllexport)
2661 # else
2662 # define LIB_API __declspec(dllimport)
2663-# ifdef NDEBUG
2664-# pragma comment(lib, "lowlevel.lib")
2665-# else
2666-# pragma comment(lib, "lowlevel_d.lib")
2667-# endif
2668 # endif
2669 # elif GCC_VERSION
2670 # ifdef LIB_BUILD
2671 # define LIB_API __attribute__ ((visibility("default")))
2672 # else
2673 # define LIB_API
2674 # endif
2675 # else
2676 # error "Don't know how to define LIB_API for this compiler"
2677 # endif
2678 #endif
2679
2680 #endif // #ifndef INCLUDED_LIB_API
2681Index: source/lib/res/graphics/cursor.cpp
2682===================================================================
2683--- source/lib/res/graphics/cursor.cpp (revision 23275)
2684+++ source/lib/res/graphics/cursor.cpp (working copy)
2685@@ -1,366 +1,356 @@
2686 /* Copyright (C) 2017 Wildfire Games.
2687 *
2688 * Permission is hereby granted, free of charge, to any person obtaining
2689 * a copy of this software and associated documentation files (the
2690 * "Software"), to deal in the Software without restriction, including
2691 * without limitation the rights to use, copy, modify, merge, publish,
2692 * distribute, sublicense, and/or sell copies of the Software, and to
2693 * permit persons to whom the Software is furnished to do so, subject to
2694 * the following conditions:
2695 *
2696 * The above copyright notice and this permission notice shall be included
2697 * in all copies or substantial portions of the Software.
2698 *
2699 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2700 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2701 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2702 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2703 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2704 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2705 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2706 */
2707
2708 /*
2709 * mouse cursors (either via OpenGL texture or hardware)
2710 */
2711
2712 #include "precompiled.h"
2713 #include "cursor.h"
2714
2715 #include <cstdio>
2716 #include <cstring>
2717 #include <sstream>
2718
2719 #include "lib/external_libraries/libsdl.h"
2720 #include "lib/ogl.h"
2721 #include "lib/res/h_mgr.h"
2722-#include "lib/sysdep/cursor.h"
2723 #include "ogl_tex.h"
2724
2725-// On Windows, allow runtime choice between system cursors and OpenGL
2726-// cursors (Windows = more responsive, OpenGL = more consistent with what
2727-// the game sees)
2728-#if OS_WIN || OS_UNIX
2729-# define ALLOW_SYS_CURSOR 1
2730-#else
2731-# define ALLOW_SYS_CURSOR 0
2732-#endif
2733-
2734 class SDLCursor
2735 {
2736 SDL_Surface* surface;
2737 SDL_Cursor* cursor;
2738
2739 public:
2740 Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale)
2741 {
2742 shared_ptr<u8> file; size_t fileSize;
2743 RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize));
2744
2745 Tex t;
2746 RETURN_STATUS_IF_ERR(t.decode(file, fileSize));
2747
2748 // convert to required BGRA format.
2749 const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT;
2750 RETURN_STATUS_IF_ERR(t.transform_to(flags));
2751 void* bgra_img = t.get_data();
2752 if(!bgra_img)
2753 WARN_RETURN(ERR::FAIL);
2754
2755 surface = SDL_CreateRGBSurfaceFrom(bgra_img, (int)t.m_Width, (int)t.m_Height, 32, (int)t.m_Width*4, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
2756 if(!surface)
2757 return ERR::FAIL;
2758 if(scale != 1.0)
2759 {
2760 SDL_Surface* scaled_surface = SDL_CreateRGBSurface(0, surface->w * scale, surface->h * scale, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
2761 if(!scaled_surface)
2762 return ERR::FAIL;
2763 if(SDL_BlitScaled(surface, NULL, scaled_surface, NULL))
2764 return ERR::FAIL;
2765 SDL_FreeSurface(surface);
2766 surface = scaled_surface;
2767 }
2768 cursor = SDL_CreateColorCursor(surface, hotspotx_, hotspoty_);
2769 if(!cursor)
2770 return ERR::FAIL;
2771
2772 return INFO::OK;
2773 }
2774
2775 void set()
2776 {
2777 SDL_SetCursor(cursor);
2778 }
2779
2780 void destroy()
2781 {
2782 SDL_FreeCursor(cursor);
2783 SDL_FreeSurface(surface);
2784 }
2785 };
2786
2787 // no init is necessary because this is stored in struct Cursor, which
2788 // is 0-initialized by h_mgr.
2789 class GLCursor
2790 {
2791 Handle ht;
2792
2793 GLint w, h;
2794 int hotspotx, hotspoty;
2795
2796 public:
2797 Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale)
2798 {
2799 ht = ogl_tex_load(vfs, pathname);
2800 RETURN_STATUS_IF_ERR(ht);
2801
2802 size_t width, height;
2803 (void)ogl_tex_get_size(ht, &width, &height, 0);
2804 w = (GLint)(width * scale);
2805 h = (GLint)(height * scale);
2806
2807 hotspotx = hotspotx_; hotspoty = hotspoty_;
2808
2809 (void)ogl_tex_set_filter(ht, GL_NEAREST);
2810 (void)ogl_tex_upload(ht);
2811 return INFO::OK;
2812 }
2813
2814 void destroy()
2815 {
2816 // note: we're stored in a resource => ht is initially 0 =>
2817 // this is safe, no need for an is_valid flag
2818 (void)ogl_tex_free(ht);
2819 }
2820
2821 void draw(int x, int y) const
2822 {
2823 #if CONFIG2_GLES
2824 UNUSED2(x); UNUSED2(y);
2825 #warning TODO: implement cursors for GLES
2826 #else
2827 (void)ogl_tex_bind(ht);
2828 glEnable(GL_TEXTURE_2D);
2829 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
2830 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2831 glEnable(GL_BLEND);
2832 glDisable(GL_DEPTH_TEST);
2833 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
2834
2835 glBegin(GL_QUADS);
2836 glTexCoord2i(1, 0); glVertex2i( x-hotspotx+w, y+hotspoty );
2837 glTexCoord2i(0, 0); glVertex2i( x-hotspotx, y+hotspoty );
2838 glTexCoord2i(0, 1); glVertex2i( x-hotspotx, y+hotspoty-h );
2839 glTexCoord2i(1, 1); glVertex2i( x-hotspotx+w, y+hotspoty-h );
2840 glEnd();
2841
2842 glDisable(GL_BLEND);
2843 glEnable(GL_DEPTH_TEST);
2844 #endif
2845 }
2846
2847 Status validate() const
2848 {
2849 const GLint A = 128; // no cursor is expected to get this big
2850 if(w > A || h > A || hotspotx > A || hotspoty > A)
2851 WARN_RETURN(ERR::_1);
2852 if(ht < 0)
2853 WARN_RETURN(ERR::_2);
2854 return INFO::OK;
2855 }
2856 };
2857
2858 enum CursorKind
2859 {
2860 CK_Default,
2861 CK_SDL,
2862 CK_OpenGL
2863 };
2864
2865 struct Cursor
2866 {
2867 double scale;
2868
2869 // require kind == CK_OpenGL after reload
2870 bool forceGL;
2871
2872 CursorKind kind;
2873
2874 // valid iff kind == CK_SDL
2875 SDLCursor sdl_cursor;
2876
2877 // valid iff kind == CK_OpenGL
2878 GLCursor gl_cursor;
2879 };
2880
2881 H_TYPE_DEFINE(Cursor);
2882
2883 static void Cursor_init(Cursor* c, va_list args)
2884 {
2885 c->scale = va_arg(args, double);
2886 c->forceGL = (va_arg(args, int) != 0);
2887 }
2888
2889 static void Cursor_dtor(Cursor* c)
2890 {
2891 switch(c->kind)
2892 {
2893 case CK_Default:
2894 break; // nothing to do
2895
2896 case CK_SDL:
2897 c->sdl_cursor.destroy();
2898 break;
2899
2900 case CK_OpenGL:
2901 c->gl_cursor.destroy();
2902 break;
2903
2904 default:
2905 DEBUG_WARN_ERR(ERR::LOGIC);
2906 break;
2907 }
2908 }
2909
2910 static Status Cursor_reload(Cursor* c, const PIVFS& vfs, const VfsPath& name, Handle)
2911 {
2912 const VfsPath pathname(VfsPath(L"art/textures/cursors") / name);
2913
2914 // read pixel offset of the cursor's hotspot [the bit of it that's
2915 // drawn at (g_mouse_x,g_mouse_y)] from file.
2916 int hotspotx = 0, hotspoty = 0;
2917 {
2918 const VfsPath pathnameHotspot = pathname.ChangeExtension(L".txt");
2919 shared_ptr<u8> buf; size_t size;
2920 RETURN_STATUS_IF_ERR(vfs->LoadFile(pathnameHotspot, buf, size));
2921 std::wstringstream s(std::wstring((const wchar_t*)buf.get(), size));
2922 s >> hotspotx >> hotspoty;
2923 }
2924
2925 const VfsPath pathnameImage = pathname.ChangeExtension(L".png");
2926
2927 // try loading as SDL2 cursor
2928 if(!c->forceGL && c->sdl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK)
2929 c->kind = CK_SDL;
2930 // fall back to GLCursor (system cursor code is disabled or failed)
2931 else if(c->gl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK)
2932 c->kind = CK_OpenGL;
2933 // everything failed, leave cursor unchanged
2934 else
2935 c->kind = CK_Default;
2936
2937 return INFO::OK;
2938 }
2939
2940 static Status Cursor_validate(const Cursor* c)
2941 {
2942 switch(c->kind)
2943 {
2944 case CK_Default:
2945 break; // nothing to do
2946
2947 case CK_SDL:
2948 break; // nothing to do
2949
2950 case CK_OpenGL:
2951 RETURN_STATUS_IF_ERR(c->gl_cursor.validate());
2952 break;
2953
2954 default:
2955 WARN_RETURN(ERR::_2);
2956 break;
2957 }
2958
2959 return INFO::OK;
2960 }
2961
2962 static Status Cursor_to_string(const Cursor* c, wchar_t* buf)
2963 {
2964 const wchar_t* type;
2965 switch(c->kind)
2966 {
2967 case CK_Default:
2968 type = L"default";
2969 break;
2970
2971 case CK_SDL:
2972 type = L"sdl";
2973 break;
2974
2975 case CK_OpenGL:
2976 type = L"gl";
2977 break;
2978
2979 default:
2980 DEBUG_WARN_ERR(ERR::LOGIC);
2981 type = L"?";
2982 break;
2983 }
2984
2985 swprintf_s(buf, H_STRING_LEN, L"cursor (%ls)", type);
2986 return INFO::OK;
2987 }
2988
2989
2990 // note: these standard resource interface functions are not exposed to the
2991-// caller. all we need here is storage for the sys_cursor / GLCursor and
2992+// caller. all we need here is storage for the GLCursor and
2993 // a name -> data lookup mechanism, both provided by h_mgr.
2994 // in other words, we continually create/free the cursor resource in
2995 // cursor_draw and trust h_mgr's caching to absorb it.
2996
2997 static Handle cursor_load(const PIVFS& vfs, const VfsPath& name, double scale, bool forceGL)
2998 {
2999 return h_alloc(H_Cursor, vfs, name, 0, scale, (int)forceGL);
3000 }
3001
3002 void cursor_shutdown()
3003 {
3004 h_mgr_free_type(H_Cursor);
3005 }
3006
3007 static Status cursor_free(Handle& h)
3008 {
3009 return h_free(h, H_Cursor);
3010 }
3011
3012
3013 Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL)
3014 {
3015 // hide the cursor
3016 if(!name)
3017 {
3018 SDL_ShowCursor(SDL_DISABLE);
3019 return INFO::OK;
3020 }
3021
3022 Handle hc = cursor_load(vfs, name, scale, forceGL);
3023 // TODO: if forceGL changes at runtime after a cursor is first created,
3024 // we might reuse a cached version of the cursor with the old forceGL flag
3025
3026 RETURN_STATUS_IF_ERR(hc); // silently ignore failures
3027
3028 H_DEREF(hc, Cursor, c);
3029
3030 switch(c->kind)
3031 {
3032 case CK_Default:
3033 break;
3034
3035 case CK_SDL:
3036 c->sdl_cursor.set();
3037 SDL_ShowCursor(SDL_ENABLE);
3038 break;
3039
3040 case CK_OpenGL:
3041 c->gl_cursor.draw(x, y);
3042 SDL_ShowCursor(SDL_DISABLE);
3043 break;
3044
3045 default:
3046 DEBUG_WARN_ERR(ERR::LOGIC);
3047 break;
3048 }
3049
3050 (void)cursor_free(hc);
3051 return INFO::OK;
3052 }
3053Index: source/lib/sysdep/clipboard.h
3054===================================================================
3055--- source/lib/sysdep/clipboard.h (revision 23275)
3056+++ source/lib/sysdep/clipboard.h (nonexistent)
3057@@ -1,38 +0,0 @@
3058-/* Copyright (C) 2011 Wildfire Games.
3059- *
3060- * Permission is hereby granted, free of charge, to any person obtaining
3061- * a copy of this software and associated documentation files (the
3062- * "Software"), to deal in the Software without restriction, including
3063- * without limitation the rights to use, copy, modify, merge, publish,
3064- * distribute, sublicense, and/or sell copies of the Software, and to
3065- * permit persons to whom the Software is furnished to do so, subject to
3066- * the following conditions:
3067- *
3068- * The above copyright notice and this permission notice shall be included
3069- * in all copies or substantial portions of the Software.
3070- *
3071- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3072- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3073- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3074- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3075- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3076- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3077- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3078- */
3079-
3080-#ifndef INCLUDED_SYSDEP_CLIPBOARD
3081-#define INCLUDED_SYSDEP_CLIPBOARD
3082-
3083-// "copy" text into the clipboard. replaces previous contents.
3084-extern Status sys_clipboard_set(const wchar_t* text);
3085-
3086-// allow "pasting" from clipboard.
3087-// @return current clipboard text or 0 if not representable as text.
3088-// callers are responsible for passing this pointer to sys_clipboard_free.
3089-extern wchar_t* sys_clipboard_get();
3090-
3091-// free memory returned by sys_clipboard_get.
3092-// @param copy is ignored if 0.
3093-extern Status sys_clipboard_free(wchar_t* copy);
3094-
3095-#endif // #ifndef INCLUDED_SYSDEP_CLIPBOARD
3096
3097Property changes on: source/lib/sysdep/clipboard.h
3098___________________________________________________________________
3099Deleted: svn:eol-style
3100## -1 +0,0 ##
3101-native
3102\ No newline at end of property
3103Index: source/lib/sysdep/cursor.h
3104===================================================================
3105--- source/lib/sysdep/cursor.h (revision 23275)
3106+++ source/lib/sysdep/cursor.h (nonexistent)
3107@@ -1,74 +0,0 @@
3108-/* Copyright (C) 2010 Wildfire Games.
3109- *
3110- * Permission is hereby granted, free of charge, to any person obtaining
3111- * a copy of this software and associated documentation files (the
3112- * "Software"), to deal in the Software without restriction, including
3113- * without limitation the rights to use, copy, modify, merge, publish,
3114- * distribute, sublicense, and/or sell copies of the Software, and to
3115- * permit persons to whom the Software is furnished to do so, subject to
3116- * the following conditions:
3117- *
3118- * The above copyright notice and this permission notice shall be included
3119- * in all copies or substantial portions of the Software.
3120- *
3121- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3122- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3123- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3124- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3125- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3126- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3127- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3128- */
3129-
3130-/*
3131- * mouse cursor
3132- */
3133-
3134-#ifndef INCLUDED_SYSDEP_CURSOR
3135-#define INCLUDED_SYSDEP_CURSOR
3136-
3137-typedef void* sys_cursor;
3138-
3139-/**
3140- * Create a cursor from the given color image.
3141- *
3142- * @param w,h Image dimensions [pixels]. the maximum value is
3143- * implementation-defined; 32x32 is typical and safe.
3144- * @param bgra_img cursor image (BGRA format, bottom-up).
3145- * It is copied and can be freed after this call returns.
3146- * @param hx,hy 'hotspot', i.e. offset from the upper-left corner to the
3147- * position where mouse clicks are registered.
3148- * @param cursor Is 0 if the return code indicates failure, otherwise
3149- * a valid cursor that must be sys_cursor_free-ed when no longer needed.
3150- **/
3151-extern Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor);
3152-
3153-/**
3154- * Create a transparent cursor (used to hide the system cursor).
3155- *
3156- * @param cursor is 0 if the return code indicates failure, otherwise
3157- * a valid cursor that must be sys_cursor_free-ed when no longer needed.
3158- **/
3159-extern Status sys_cursor_create_empty(sys_cursor* cursor);
3160-
3161-/**
3162- * override the current system cursor.
3163- *
3164- * @param cursor can be 0 to restore the default.
3165- **/
3166-extern Status sys_cursor_set(sys_cursor cursor);
3167-
3168-/**
3169- * destroy the indicated cursor and frees its resources.
3170- *
3171- * @param cursor if currently in use, the default cursor is restored first.
3172- **/
3173-extern Status sys_cursor_free(sys_cursor cursor);
3174-
3175-/**
3176- * reset any cached cursor data.
3177- * on some systems, this is needed when resetting the SDL video subsystem.
3178- **/
3179-extern Status sys_cursor_reset();
3180-
3181-#endif // #ifndef INCLUDED_SYSDEP_CURSOR
3182
3183Property changes on: source/lib/sysdep/cursor.h
3184___________________________________________________________________
3185Deleted: svn:eol-style
3186## -1 +0,0 ##
3187-native
3188\ No newline at end of property
3189Index: source/lib/sysdep/gfx.cpp
3190===================================================================
3191--- source/lib/sysdep/gfx.cpp (revision 23275)
3192+++ source/lib/sysdep/gfx.cpp (working copy)
3193@@ -1,101 +1,92 @@
3194 /* Copyright (C) 2015 Wildfire Games.
3195 *
3196 * Permission is hereby granted, free of charge, to any person obtaining
3197 * a copy of this software and associated documentation files (the
3198 * "Software"), to deal in the Software without restriction, including
3199 * without limitation the rights to use, copy, modify, merge, publish,
3200 * distribute, sublicense, and/or sell copies of the Software, and to
3201 * permit persons to whom the Software is furnished to do so, subject to
3202 * the following conditions:
3203 *
3204 * The above copyright notice and this permission notice shall be included
3205 * in all copies or substantial portions of the Software.
3206 *
3207 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3208 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3209 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3210 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3211 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3212 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3213 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3214 */
3215
3216 /*
3217 * graphics card detection.
3218 */
3219
3220 #include "precompiled.h"
3221 #include "lib/sysdep/gfx.h"
3222
3223 #include "lib/external_libraries/libsdl.h"
3224 #include "lib/ogl.h"
3225
3226 #if OS_WIN
3227 # include "lib/sysdep/os/win/wgfx.h"
3228 #endif
3229
3230
3231 namespace gfx {
3232
3233 std::wstring CardName()
3234 {
3235 // GL_VENDOR+GL_RENDERER are good enough here, so we don't use WMI to detect the cards.
3236 // On top of that WMI can cause crashes with Nvidia Optimus and some netbooks
3237 // see http://trac.wildfiregames.com/ticket/1952
3238 // http://trac.wildfiregames.com/ticket/1575
3239 wchar_t cardName[128];
3240 const char* vendor = (const char*)glGetString(GL_VENDOR);
3241 const char* renderer = (const char*)glGetString(GL_RENDERER);
3242 // (happens if called before ogl_Init or between glBegin and glEnd.)
3243 if(!vendor || !renderer)
3244 return L"";
3245 swprintf_s(cardName, ARRAY_SIZE(cardName), L"%hs %hs", vendor, renderer);
3246
3247 // remove crap from vendor names. (don't dare touch the model name -
3248 // it's too risky, there are too many different strings)
3249 #define SHORTEN(what, charsToKeep)\
3250 if(!wcsncmp(cardName, what, ARRAY_SIZE(what)-1))\
3251 memmove(cardName+charsToKeep, cardName+ARRAY_SIZE(what)-1, (wcslen(cardName)-(ARRAY_SIZE(what)-1)+1)*sizeof(wchar_t));
3252 SHORTEN(L"ATI Technologies Inc.", 3);
3253 SHORTEN(L"NVIDIA Corporation", 6);
3254 SHORTEN(L"S3 Graphics", 2); // returned by EnumDisplayDevices
3255 SHORTEN(L"S3 Graphics, Incorporated", 2); // returned by GL_VENDOR
3256 #undef SHORTEN
3257
3258 return cardName;
3259 }
3260
3261
3262 std::wstring DriverInfo()
3263 {
3264 std::wstring driverInfo;
3265 #if OS_WIN
3266 driverInfo = wgfx_DriverInfo();
3267 if(driverInfo.empty())
3268 #endif
3269 {
3270 const char* version = (const char*)glGetString(GL_VERSION);
3271 if(version)
3272 {
3273 // add "OpenGL" to differentiate this from the real driver version
3274 // (returned by platform-specific detect routines).
3275 driverInfo = std::wstring(L"OpenGL ") + std::wstring(version, version+strlen(version));
3276 }
3277 }
3278
3279 if(driverInfo.empty())
3280 return L"(unknown)";
3281 return driverInfo;
3282 }
3283
3284
3285-size_t MemorySizeMiB()
3286-{
3287- // TODO: not implemented, SDL_GetVideoInfo only works on some platforms in SDL 1.2
3288- // and no replacement is available in SDL2, and it can crash with Nvidia Optimus
3289- // see http://trac.wildfiregames.com/ticket/2145
3290- debug_warn(L"MemorySizeMiB not implemented");
3291- return 0;
3292-}
3293-
3294 } // namespace gfx
3295Index: source/lib/sysdep/gfx.h
3296===================================================================
3297--- source/lib/sysdep/gfx.h (revision 23275)
3298+++ source/lib/sysdep/gfx.h (working copy)
3299@@ -1,70 +1,46 @@
3300 /* Copyright (C) 2013 Wildfire Games.
3301 *
3302 * Permission is hereby granted, free of charge, to any person obtaining
3303 * a copy of this software and associated documentation files (the
3304 * "Software"), to deal in the Software without restriction, including
3305 * without limitation the rights to use, copy, modify, merge, publish,
3306 * distribute, sublicense, and/or sell copies of the Software, and to
3307 * permit persons to whom the Software is furnished to do so, subject to
3308 * the following conditions:
3309 *
3310 * The above copyright notice and this permission notice shall be included
3311 * in all copies or substantial portions of the Software.
3312 *
3313 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3314 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3315 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3316 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3317 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3318 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3319 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3320 */
3321
3322 /*
3323 * graphics card detection.
3324 */
3325
3326 #ifndef INCLUDED_GFX
3327 #define INCLUDED_GFX
3328
3329 namespace gfx {
3330
3331 /**
3332 * @return description of graphics card,
3333 * or L"" if unknown.
3334 **/
3335 LIB_API std::wstring CardName();
3336
3337 /**
3338 * @return string describing the graphics driver and its version,
3339 * or L"" if unknown.
3340 **/
3341 LIB_API std::wstring DriverInfo();
3342
3343-/**
3344- * not implemented
3345- **/
3346-LIB_API size_t MemorySizeMiB();
3347-
3348-/**
3349- * (useful for choosing a new video mode)
3350- *
3351- * @param xres, yres (optional out) resolution [pixels]
3352- * @param bpp (optional out) bits per pixel
3353- * @param freq (optional out) vertical refresh rate [Hz]
3354- * @return Status (if negative, outputs were left unchanged)
3355- **/
3356-LIB_API Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq);
3357-
3358-/**
3359- * (useful for determining aspect ratio)
3360- *
3361- * @param width_mm (out) screen width [mm]
3362- * @param height_mm (out) screen height [mm]
3363- * @return Status (if if negative, outputs were left unchanged)
3364- **/
3365-LIB_API Status GetMonitorSize(int& width_mm, int& height_mm);
3366-
3367 } // namespace gfx
3368
3369 #endif // #ifndef INCLUDED_GFX
3370Index: source/lib/sysdep/os/android/android.cpp
3371===================================================================
3372--- source/lib/sysdep/os/android/android.cpp (revision 23275)
3373+++ source/lib/sysdep/os/android/android.cpp (nonexistent)
3374@@ -1,120 +0,0 @@
3375-/* Copyright (C) 2012 Wildfire Games.
3376- *
3377- * Permission is hereby granted, free of charge, to any person obtaining
3378- * a copy of this software and associated documentation files (the
3379- * "Software"), to deal in the Software without restriction, including
3380- * without limitation the rights to use, copy, modify, merge, publish,
3381- * distribute, sublicense, and/or sell copies of the Software, and to
3382- * permit persons to whom the Software is furnished to do so, subject to
3383- * the following conditions:
3384- *
3385- * The above copyright notice and this permission notice shall be included
3386- * in all copies or substantial portions of the Software.
3387- *
3388- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3389- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3390- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3391- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3392- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3393- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3394- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3395- */
3396-
3397-#include "precompiled.h"
3398-
3399-#include "lib/sysdep/sysdep.h"
3400-#include "lib/sysdep/cursor.h"
3401-
3402-#include "lib/external_libraries/libsdl.h"
3403-
3404-Status sys_clipboard_set(const wchar_t* UNUSED(text))
3405-{
3406- return INFO::OK;
3407-}
3408-
3409-wchar_t* sys_clipboard_get()
3410-{
3411- return NULL;
3412-}
3413-
3414-Status sys_clipboard_free(wchar_t* UNUSED(copy))
3415-{
3416- return INFO::OK;
3417-}
3418-
3419-namespace gfx {
3420-
3421-Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
3422-{
3423-#warning TODO: implement gfx::GetVideoMode properly for Android
3424-
3425- if(xres)
3426- *xres = 800;
3427-
3428- if(yres)
3429- *yres = 480;
3430-
3431- if(bpp)
3432- *bpp = 32;
3433-
3434- if(freq)
3435- *freq = 0;
3436-
3437- return INFO::OK;
3438-}
3439-
3440-}
3441-
3442-// stub implementation of sys_cursor* functions
3443-
3444-// note: do not return ERR_NOT_IMPLEMENTED or similar because that
3445-// would result in WARN_ERRs.
3446-Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
3447-{
3448- UNUSED2(w);
3449- UNUSED2(h);
3450- UNUSED2(hx);
3451- UNUSED2(hy);
3452- UNUSED2(bgra_img);
3453-
3454- *cursor = 0;
3455- return INFO::OK;
3456-}
3457-
3458-// returns a dummy value representing an empty cursor
3459-Status sys_cursor_create_empty(sys_cursor* cursor)
3460-{
3461- *cursor = (void*)1; // any non-zero value, since the cursor NULL has special meaning
3462- return INFO::OK;
3463-}
3464-
3465-// replaces the current system cursor with the one indicated. need only be
3466-// called once per cursor; pass 0 to restore the default.
3467-Status sys_cursor_set(sys_cursor cursor)
3468-{
3469- if (cursor) // dummy empty cursor
3470- SDL_ShowCursor(SDL_DISABLE);
3471- else // restore default cursor
3472- SDL_ShowCursor(SDL_ENABLE);
3473-
3474- return INFO::OK;
3475-}
3476-
3477-// destroys the indicated cursor and frees its resources. if it is
3478-// currently the system cursor, the default cursor is restored first.
3479-Status sys_cursor_free(sys_cursor cursor)
3480-{
3481- // bail now to prevent potential confusion below; there's nothing to do.
3482- if(!cursor)
3483- return INFO::OK;
3484-
3485- SDL_ShowCursor(SDL_ENABLE);
3486-
3487- return INFO::OK;
3488-}
3489-
3490-Status sys_cursor_reset()
3491-{
3492- return INFO::OK;
3493-}
3494-
3495
3496Property changes on: source/lib/sysdep/os/android/android.cpp
3497___________________________________________________________________
3498Deleted: svn:eol-style
3499## -1 +0,0 ##
3500-native
3501\ No newline at end of property
3502Index: source/lib/sysdep/os/osx/osx.cpp
3503===================================================================
3504--- source/lib/sysdep/os/osx/osx.cpp (revision 23275)
3505+++ source/lib/sysdep/os/osx/osx.cpp (working copy)
3506@@ -1,186 +1,66 @@
3507 /* Copyright (C) 2015 Wildfire Games.
3508 *
3509 * Permission is hereby granted, free of charge, to any person obtaining
3510 * a copy of this software and associated documentation files (the
3511 * "Software"), to deal in the Software without restriction, including
3512 * without limitation the rights to use, copy, modify, merge, publish,
3513 * distribute, sublicense, and/or sell copies of the Software, and to
3514 * permit persons to whom the Software is furnished to do so, subject to
3515 * the following conditions:
3516 *
3517 * The above copyright notice and this permission notice shall be included
3518 * in all copies or substantial portions of the Software.
3519 *
3520 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3521 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3522 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3523 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3524 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3525 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3526 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3527 */
3528
3529 #include "precompiled.h"
3530
3531 #include "lib/lib.h"
3532 #include "lib/sysdep/sysdep.h"
3533 #include "lib/sysdep/gfx.h"
3534 #include "lib/utf8.h"
3535 #include "osx_bundle.h"
3536-#include "osx_pasteboard.h"
3537
3538 #include <ApplicationServices/ApplicationServices.h>
3539 #include <AvailabilityMacros.h> // MAC_OS_X_VERSION_MIN_REQUIRED
3540 #include <CoreFoundation/CoreFoundation.h>
3541 #include <mach-o/dyld.h> // _NSGetExecutablePath
3542
3543 // Ignore deprecation warnings for 10.5 backwards compatibility
3544 #pragma GCC diagnostic push
3545 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
3546
3547-Status sys_clipboard_set(const wchar_t* text)
3548-{
3549- Status ret = INFO::OK;
3550-
3551- std::string str = utf8_from_wstring(text);
3552- bool ok = osx_SendStringToPasteboard(str);
3553- if (!ok)
3554- ret = ERR::FAIL;
3555- return ret;
3556-}
3557-
3558-wchar_t* sys_clipboard_get()
3559-{
3560- wchar_t* ret = NULL;
3561- std::string str;
3562- bool ok = osx_GetStringFromPasteboard(str);
3563- if (ok)
3564- {
3565- // TODO: this is yucky, why are we passing around wchar_t*?
3566- std::wstring wstr = wstring_from_utf8(str);
3567- size_t len = wcslen(wstr.c_str());
3568- ret = (wchar_t*)malloc((len+1)*sizeof(wchar_t));
3569- std::copy(wstr.c_str(), wstr.c_str()+len, ret);
3570- ret[len] = 0;
3571- }
3572-
3573- return ret;
3574-}
3575-
3576-Status sys_clipboard_free(wchar_t* copy)
3577-{
3578- free(copy);
3579- return INFO::OK;
3580-}
3581-
3582-
3583-namespace gfx {
3584-
3585-Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
3586-{
3587- if(xres)
3588- *xres = (int)CGDisplayPixelsWide(kCGDirectMainDisplay);
3589-
3590- if(yres)
3591- *yres = (int)CGDisplayPixelsHigh(kCGDirectMainDisplay);
3592-
3593- if(bpp)
3594- {
3595-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3596- // CGDisplayBitsPerPixel was deprecated in OS X 10.6
3597- if (CGDisplayCopyDisplayMode != NULL)
3598- {
3599- CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay);
3600- CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(currentMode);
3601- if (CFStringCompare(pixelEncoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
3602- *bpp = 32;
3603- else if (CFStringCompare(pixelEncoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
3604- *bpp = 16;
3605- else if (CFStringCompare(pixelEncoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
3606- *bpp = 8;
3607- else // error
3608- *bpp = 0;
3609-
3610- // We're responsible for this
3611- CFRelease(pixelEncoding);
3612- CGDisplayModeRelease(currentMode);
3613- }
3614- else
3615- {
3616-#endif // fallback to 10.5 API
3617- CFDictionaryRef currentMode = CGDisplayCurrentMode(kCGDirectMainDisplay);
3618- CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(currentMode, kCGDisplayBitsPerPixel);
3619- CFNumberGetValue(num, kCFNumberIntType, bpp);
3620-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3621- }
3622-#endif
3623- }
3624-
3625- if(freq)
3626- {
3627-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3628- if (CGDisplayCopyDisplayMode != NULL)
3629- {
3630- CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay);
3631- *freq = (int)CGDisplayModeGetRefreshRate(currentMode);
3632-
3633- // We're responsible for this
3634- CGDisplayModeRelease(currentMode);
3635- }
3636- else
3637- {
3638-#endif // fallback to 10.5 API
3639- CFDictionaryRef currentMode = CGDisplayCurrentMode(kCGDirectMainDisplay);
3640- CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(currentMode, kCGDisplayRefreshRate);
3641- CFNumberGetValue(num, kCFNumberIntType, freq);
3642-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3643- }
3644-#endif
3645- }
3646-
3647- return INFO::OK;
3648-}
3649-
3650-Status GetMonitorSize(int& width_mm, int& height_mm)
3651-{
3652- CGSize screenSize = CGDisplayScreenSize(kCGDirectMainDisplay);
3653-
3654- if (screenSize.width == 0 || screenSize.height == 0)
3655- return ERR::FAIL;
3656-
3657- width_mm = screenSize.width;
3658- height_mm = screenSize.height;
3659-
3660- return INFO::OK;
3661-}
3662-
3663-} // namespace gfx
3664-
3665-
3666 OsPath sys_ExecutablePathname()
3667 {
3668 OsPath path;
3669
3670 // On OS X we might be a bundle, return the bundle path as the executable name,
3671 // i.e. /path/to/0ad.app instead of /path/to/0ad.app/Contents/MacOS/pyrogenesis
3672 if (osx_IsAppBundleValid())
3673 {
3674 path = osx_GetBundlePath();
3675 ENSURE(!path.empty());
3676 }
3677 else
3678 {
3679 char temp[PATH_MAX];
3680 u32 size = PATH_MAX;
3681 if (_NSGetExecutablePath(temp, &size) == 0)
3682 {
3683 char name[PATH_MAX];
3684 realpath(temp, name);
3685 path = OsPath(name);
3686 }
3687 }
3688
3689 return path;
3690 }
3691
3692 #pragma GCC diagnostic pop // restore user flags
3693Index: source/lib/sysdep/os/osx/osx_pasteboard.h
3694===================================================================
3695--- source/lib/sysdep/os/osx/osx_pasteboard.h (revision 23275)
3696+++ source/lib/sysdep/os/osx/osx_pasteboard.h (nonexistent)
3697@@ -1,47 +0,0 @@
3698-/* Copyright (C) 2012 Wildfire Games.
3699- *
3700- * Permission is hereby granted, free of charge, to any person obtaining
3701- * a copy of this software and associated documentation files (the
3702- * "Software"), to deal in the Software without restriction, including
3703- * without limitation the rights to use, copy, modify, merge, publish,
3704- * distribute, sublicense, and/or sell copies of the Software, and to
3705- * permit persons to whom the Software is furnished to do so, subject to
3706- * the following conditions:
3707- *
3708- * The above copyright notice and this permission notice shall be included
3709- * in all copies or substantial portions of the Software.
3710- *
3711- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3712- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3713- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3714- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3715- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3716- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3717- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3718- */
3719-
3720-#ifndef OSX_PASTEBOARD_H
3721-#define OSX_PASTEBOARD_H
3722-
3723-/**
3724- * @file
3725- * C++ interface to Cocoa implementation for pasteboards
3726- */
3727-
3728-/**
3729- * Get a string from the pasteboard
3730- *
3731- * @param[out] out pasteboard string in UTF-8 encoding, if found
3732- * @return true if string was found on pasteboard and successfully retrieved, false otherwise
3733- */
3734-bool osx_GetStringFromPasteboard(std::string& out);
3735-
3736-/**
3737- * Store a string on the pasteboard
3738- *
3739- * @param[in] string string to store in UTF-8 encoding
3740- * @return true if string was successfully sent to pasteboard, false on error
3741- */
3742-bool osx_SendStringToPasteboard(const std::string& string);
3743-
3744-#endif // OSX_PASTEBOARD_H
3745
3746Property changes on: source/lib/sysdep/os/osx/osx_pasteboard.h
3747___________________________________________________________________
3748Deleted: svn:eol-style
3749## -1 +0,0 ##
3750-native
3751\ No newline at end of property
3752Index: source/lib/sysdep/os/osx/osx_pasteboard.mm
3753===================================================================
3754--- source/lib/sysdep/os/osx/osx_pasteboard.mm (revision 23275)
3755+++ source/lib/sysdep/os/osx/osx_pasteboard.mm (nonexistent)
3756@@ -1,95 +0,0 @@
3757-/* Copyright (C) 2013 Wildfire Games.
3758- *
3759- * Permission is hereby granted, free of charge, to any person obtaining
3760- * a copy of this software and associated documentation files (the
3761- * "Software"), to deal in the Software without restriction, including
3762- * without limitation the rights to use, copy, modify, merge, publish,
3763- * distribute, sublicense, and/or sell copies of the Software, and to
3764- * permit persons to whom the Software is furnished to do so, subject to
3765- * the following conditions:
3766- *
3767- * The above copyright notice and this permission notice shall be included
3768- * in all copies or substantial portions of the Software.
3769- *
3770- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3771- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3772- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3773- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3774- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3775- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3776- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3777- */
3778-
3779-#import <AppKit/AppKit.h>
3780-#import <AvailabilityMacros.h> // MAC_OS_X_VERSION_MIN_REQUIRED
3781-#import <Foundation/Foundation.h>
3782-#import <string>
3783-
3784-#import "osx_pasteboard.h"
3785-
3786-bool osx_GetStringFromPasteboard(std::string& out)
3787-{
3788- NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
3789- NSString* string = nil;
3790-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3791- // As of 10.6, pasteboards can hold multiple items
3792- if ([pasteboard respondsToSelector: @selector(readObjectsForClasses:)])
3793- {
3794- NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil];
3795- NSDictionary* options = [NSDictionary dictionary];
3796- NSArray* copiedItems = [pasteboard readObjectsForClasses:classes options:options];
3797- // We only need to support a single item, so grab the first string
3798- if (copiedItems != nil && [copiedItems count] > 0)
3799- string = [copiedItems objectAtIndex:0];
3800- else
3801- return false; // No strings found on pasteboard
3802- }
3803- else
3804- {
3805-#endif // fallback to 10.5 API
3806- // Verify that there is a string available for us
3807- NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
3808- if ([pasteboard availableTypeFromArray:types] != nil)
3809- string = [pasteboard stringForType:NSStringPboardType];
3810- else
3811- return false; // No strings found on pasteboard
3812-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3813- }
3814-#endif
3815-
3816- if (string != nil)
3817- out = std::string([string UTF8String]);
3818- else
3819- return false; // fail
3820-
3821- return true; // success
3822-}
3823-
3824-bool osx_SendStringToPasteboard(const std::string& string)
3825-{
3826- // We're only working with strings, so we don't need to lazily write
3827- // anything (otherwise we'd need to set up an owner and data provider)
3828- NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
3829- NSString* type;
3830-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3831- if ([pasteboard respondsToSelector: @selector(clearContents)])
3832- {
3833- type = NSPasteboardTypeString;
3834- [pasteboard clearContents];
3835- }
3836- else
3837- {
3838-#endif // fallback to 10.5 API
3839- type = NSStringPboardType;
3840- NSArray* types = [NSArray arrayWithObjects: type, nil];
3841- // Roughly equivalent to clearContents followed by addTypes:owner
3842- [pasteboard declareTypes:types owner:nil];
3843-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3844-
3845- }
3846-#endif
3847-
3848- // May raise a NSPasteboardCommunicationException
3849- BOOL ok = [pasteboard setString:[NSString stringWithUTF8String:string.c_str()] forType:type];
3850- return ok == YES;
3851-}
3852
3853Property changes on: source/lib/sysdep/os/osx/osx_pasteboard.mm
3854___________________________________________________________________
3855Deleted: svn:eol-style
3856## -1 +0,0 ##
3857-native
3858\ No newline at end of property
3859Index: source/lib/sysdep/os/osx/osx_sys_cursor.mm
3860===================================================================
3861--- source/lib/sysdep/os/osx/osx_sys_cursor.mm (revision 23275)
3862+++ source/lib/sysdep/os/osx/osx_sys_cursor.mm (nonexistent)
3863@@ -1,104 +0,0 @@
3864-/* Copyright (C) 2012 Wildfire Games.
3865- *
3866- * Permission is hereby granted, free of charge, to any person obtaining
3867- * a copy of this software and associated documentation files (the
3868- * "Software"), to deal in the Software without restriction, including
3869- * without limitation the rights to use, copy, modify, merge, publish,
3870- * distribute, sublicense, and/or sell copies of the Software, and to
3871- * permit persons to whom the Software is furnished to do so, subject to
3872- * the following conditions:
3873- *
3874- * The above copyright notice and this permission notice shall be included
3875- * in all copies or substantial portions of the Software.
3876- *
3877- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3878- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3879- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3880- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3881- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3882- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3883- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3884- */
3885-
3886-#import "precompiled.h"
3887-#import "lib/sysdep/cursor.h"
3888-
3889-#import <AppKit/NSCursor.h>
3890-#import <AppKit/NSImage.h>
3891-#import <ApplicationServices/ApplicationServices.h>
3892-
3893-//TODO: make sure these are threadsafe
3894-Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
3895-{
3896- NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc]
3897- initWithBitmapDataPlanes:0 pixelsWide:w pixelsHigh:h
3898- bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
3899- colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:w*4 bitsPerPixel:0];
3900- if (!bitmap)
3901- {
3902- debug_printf("sys_cursor_create: Error creating NSBitmapImageRep!\n");
3903- return ERR::FAIL;
3904- }
3905-
3906- u8* planes[5];
3907- [bitmap getBitmapDataPlanes:planes];
3908- const u8* bgra = static_cast<const u8*>(bgra_img);
3909- u8* dst = planes[0];
3910- for (int i = 0; i < w*h*4; i += 4)
3911- {
3912- dst[i] = bgra[i+2];
3913- dst[i+1] = bgra[i+1];
3914- dst[i+2] = bgra[i];
3915- dst[i+3] = bgra[i+3];
3916- }
3917-
3918- NSImage* image = [[NSImage alloc] init];
3919- if (!image)
3920- {
3921- [bitmap release];
3922- debug_printf("sys_cursor_create: Error creating NSImage!\n");
3923- return ERR::FAIL;
3924- }
3925-
3926- [image addRepresentation:bitmap];
3927- [bitmap release];
3928- NSCursor* impl = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(hx, hy)];
3929- [image release];
3930-
3931- if (!impl)
3932- {
3933- debug_printf("sys_cursor_create: Error creating NSCursor!\n");
3934- return ERR::FAIL;
3935- }
3936-
3937- *cursor = static_cast<sys_cursor>(impl);
3938-
3939- return INFO::OK;
3940-}
3941-
3942-Status sys_cursor_free(sys_cursor cursor)
3943-{
3944- NSCursor* impl = static_cast<NSCursor*>(cursor);
3945- [impl release];
3946- return INFO::OK;
3947-}
3948-
3949-Status sys_cursor_create_empty(sys_cursor* cursor)
3950-{
3951- static u8 empty_bgra[] = {0, 0, 0, 0};
3952- sys_cursor_create(1, 1, reinterpret_cast<void*>(empty_bgra), 0, 0, cursor);
3953- return INFO::OK;
3954-}
3955-
3956-Status sys_cursor_set(sys_cursor cursor)
3957-{
3958- NSCursor* impl = static_cast<NSCursor*>(cursor);
3959- [impl set];
3960- return INFO::OK;
3961-}
3962-
3963-Status sys_cursor_reset()
3964-{
3965- return INFO::OK;
3966-}
3967-
3968
3969Property changes on: source/lib/sysdep/os/osx/osx_sys_cursor.mm
3970___________________________________________________________________
3971Deleted: svn:eol-style
3972## -1 +0,0 ##
3973-native
3974\ No newline at end of property
3975Index: source/lib/sysdep/os/unix/unix_executable_pathname.cpp
3976===================================================================
3977--- source/lib/sysdep/os/unix/unix_executable_pathname.cpp (revision 23275)
3978+++ source/lib/sysdep/os/unix/unix_executable_pathname.cpp (working copy)
3979@@ -1,73 +1,72 @@
3980 /* Copyright (C) 2014 Wildfire Games.
3981 *
3982 * Permission is hereby granted, free of charge, to any person obtaining
3983 * a copy of this software and associated documentation files (the
3984 * "Software"), to deal in the Software without restriction, including
3985 * without limitation the rights to use, copy, modify, merge, publish,
3986 * distribute, sublicense, and/or sell copies of the Software, and to
3987 * permit persons to whom the Software is furnished to do so, subject to
3988 * the following conditions:
3989 *
3990 * The above copyright notice and this permission notice shall be included
3991 * in all copies or substantial portions of the Software.
3992 *
3993 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3994 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3995 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3996 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3997 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3998 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3999 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4000 */
4001
4002 #include "precompiled.h"
4003
4004 #include "lib/sysdep/sysdep.h"
4005
4006-#define GNU_SOURCE
4007-#include "mocks/dlfcn.h"
4008-#include "mocks/unistd.h"
4009-
4010+#define _GNU_SOURCE
4011+#include <dlfcn.h>
4012 #include <cstdio>
4013+#include <unistd.h>
4014
4015 OsPath unix_ExecutablePathname()
4016 {
4017 // Find the executable's filename
4018 Dl_info dl_info;
4019 memset(&dl_info, 0, sizeof(dl_info));
4020- if (!T::dladdr((void *)sys_ExecutablePathname, &dl_info) || !dl_info.dli_fname)
4021+ if (!dladdr((void *)sys_ExecutablePathname, &dl_info) || !dl_info.dli_fname)
4022 return OsPath();
4023 const char* path = dl_info.dli_fname;
4024
4025 // If this looks like an absolute path, use realpath to get the normalized
4026 // path (with no '.' or '..')
4027 if (path[0] == '/')
4028 {
4029 char resolved[PATH_MAX];
4030 if (!realpath(path, resolved))
4031 return OsPath();
4032 return resolved;
4033 }
4034
4035 // If this looks like a relative path, resolve against cwd and use realpath
4036 if (strchr(path, '/'))
4037 {
4038 char cwd[PATH_MAX];
4039- if (!T::getcwd(cwd, PATH_MAX))
4040+ if (!getcwd(cwd, PATH_MAX))
4041 return OsPath();
4042
4043 char absolute[PATH_MAX];
4044 int ret = snprintf(absolute, PATH_MAX, "%s/%s", cwd, path);
4045 if (ret < 0 || ret >= PATH_MAX)
4046 return OsPath(); // path too long, or other error
4047 char resolved[PATH_MAX];
4048 if (!realpath(absolute, resolved))
4049 return OsPath();
4050 return resolved;
4051 }
4052
4053 // If it's not a path at all, i.e. it's just a filename, we'd
4054 // probably have to search through PATH to find it.
4055 // That's complex and should be uncommon, so don't bother.
4056 return OsPath();
4057 }
4058Index: source/lib/sysdep/os/unix/x/x.cpp
4059===================================================================
4060--- source/lib/sysdep/os/unix/x/x.cpp (revision 23275)
4061+++ source/lib/sysdep/os/unix/x/x.cpp (nonexistent)
4062@@ -1,491 +0,0 @@
4063-/* Copyright (C) 2019 Wildfire Games.
4064- *
4065- * Permission is hereby granted, free of charge, to any person obtaining
4066- * a copy of this software and associated documentation files (the
4067- * "Software"), to deal in the Software without restriction, including
4068- * without limitation the rights to use, copy, modify, merge, publish,
4069- * distribute, sublicense, and/or sell copies of the Software, and to
4070- * permit persons to whom the Software is furnished to do so, subject to
4071- * the following conditions:
4072- *
4073- * The above copyright notice and this permission notice shall be included
4074- * in all copies or substantial portions of the Software.
4075- *
4076- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4077- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4078- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4079- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4080- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4081- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4082- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4083- */
4084-
4085-// X Window System-specific code
4086-
4087-#include "precompiled.h"
4088-
4089-#if OS_LINUX || OS_BSD
4090-# define HAVE_X 1
4091-#else
4092-# define HAVE_X 0
4093-#endif
4094-
4095-#if HAVE_X
4096-
4097-#include "lib/debug.h"
4098-#include "lib/utf8.h"
4099-#include "lib/sysdep/gfx.h"
4100-#include "lib/sysdep/cursor.h"
4101-
4102-#include "ps/VideoMode.h"
4103-
4104-#define Cursor X__Cursor
4105-
4106-#include <X11/Xlib.h>
4107-#include <stdlib.h>
4108-#include <X11/Xatom.h>
4109-#include <X11/Xcursor/Xcursor.h>
4110-
4111-#include "SDL.h"
4112-#include "SDL_syswm.h"
4113-
4114-#include <algorithm>
4115-#undef Cursor
4116-#undef Status
4117-
4118-static Display *g_SDL_Display;
4119-static Window g_SDL_Window;
4120-static wchar_t *selection_data=NULL;
4121-static size_t selection_size=0;
4122-
4123-namespace gfx {
4124-
4125-Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
4126-{
4127- Display* disp = XOpenDisplay(0);
4128- if(!disp)
4129- WARN_RETURN(ERR::FAIL);
4130-
4131- int screen = XDefaultScreen(disp);
4132-
4133- /* 2004-07-13
4134- NOTE: The XDisplayWidth/Height functions don't actually return the current
4135- display mode - they return the size of the root window. This means that
4136- users with "Virtual Desktops" bigger than what their monitors/graphics
4137- card can handle will have to set their 0AD screen resolution manually.
4138-
4139- There's supposed to be an X extension that can give you the actual display
4140- mode, probably including refresh rate info etc, but it's not worth
4141- researching and implementing that at this stage.
4142- */
4143-
4144- if(xres)
4145- *xres = XDisplayWidth(disp, screen);
4146- if(yres)
4147- *yres = XDisplayHeight(disp, screen);
4148- if(bpp)
4149- *bpp = XDefaultDepth(disp, screen);
4150- if(freq)
4151- *freq = 0;
4152- XCloseDisplay(disp);
4153- return INFO::OK;
4154-}
4155-
4156-
4157-Status GetMonitorSize(int& width_mm, int& height_mm)
4158-{
4159- Display* disp = XOpenDisplay(0);
4160- if(!disp)
4161- WARN_RETURN(ERR::FAIL);
4162-
4163- int screen = XDefaultScreen(disp);
4164-
4165- width_mm = XDisplayWidthMM(disp, screen);
4166- height_mm = XDisplayHeightMM(disp, screen);
4167-
4168- XCloseDisplay(disp);
4169- return INFO::OK;
4170-}
4171-
4172-} // namespace gfx
4173-
4174-
4175-static bool get_wminfo(SDL_SysWMinfo& wminfo)
4176-{
4177- SDL_VERSION(&wminfo.version);
4178-
4179- const int ret = SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &wminfo);
4180-
4181- if(ret == 1)
4182- return true;
4183-
4184- if(ret == -1)
4185- {
4186- debug_printf("SDL_GetWMInfo failed\n");
4187- return false;
4188- }
4189- if(ret == 0)
4190- {
4191- debug_printf("SDL_GetWMInfo is not implemented on this platform\n");
4192- return false;
4193- }
4194-
4195- debug_printf("SDL_GetWMInfo returned an unknown value: %d\n", ret);
4196- return false;
4197-}
4198-
4199-/*
4200-Oh, boy, this is heavy stuff...
4201-
4202-<User-End Stuff - Definitions and Conventions>
4203-http://www.freedesktop.org/standards/clipboards-spec/clipboards.txt
4204-<Technical, API stuff>
4205-http://www.mail-archive.com/xfree86@xfree86.org/msg15594.html
4206-http://michael.toren.net/mirrors/doc/X-copy+paste.txt
4207-http://devdocs.wesnoth.org/clipboard_8cpp-source.html
4208-http://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html
4209-http://www.jwz.org/doc/x-cut-and-paste.html
4210-
4211-The basic run-down on X Selection Handling:
4212-* One window owns the "current selection" at any one time
4213-* Accessing the Selection (i.e. "paste"), Step-by-step
4214- * Ask the X server for the current selection owner
4215- * Ask the selection owner window to convert the selection into a format
4216- we can understand (XA_STRING - Latin-1 string - for now)
4217- * The converted result is stored as a property of the *selection owner*
4218- window. It is possible to specify the current application window as the
4219- target - but that'd require some X message handling... easier to skip that..
4220- * The final step is to acquire the property value of the selection owner
4221- window
4222-
4223-Notes:
4224-An "Atom" is a server-side object that represents a string by an index into some
4225-kind of table or something. Pretty much like a handle that refers to one unique
4226-string. Atoms are used here to refer to property names and value formats.
4227-
4228-Expansions:
4229-* Implement UTF-8 format support (should be interresting for international users)
4230-
4231-*/
4232-wchar_t *sys_clipboard_get()
4233-{
4234- Display *disp=XOpenDisplay(NULL);
4235- if(!disp)
4236- return NULL;
4237-
4238- // We use CLIPBOARD as the default, since the CLIPBOARD semantics are much
4239- // closer to windows semantics.
4240- Atom selSource=XInternAtom(disp, "CLIPBOARD", False);
4241-
4242- Window selOwner=XGetSelectionOwner(disp, selSource);
4243- if(selOwner == None)
4244- {
4245- // However, since many apps don't use CLIPBOARD, but use PRIMARY instead
4246- // we use XA_PRIMARY as a fallback clipboard. This is true for xterm,
4247- // for example.
4248- selSource=XA_PRIMARY;
4249- selOwner=XGetSelectionOwner(disp, selSource);
4250- }
4251- if(selOwner != None) {
4252- Atom pty=XInternAtom(disp, "SelectionPropertyTemp", False);
4253- XConvertSelection(disp, selSource, XA_STRING, pty, selOwner, CurrentTime);
4254- XFlush(disp);
4255-
4256- Atom type;
4257- int format=0, result=0;
4258- unsigned long len=0, bytes_left=0, dummy=0;
4259- u8 *data=NULL;
4260-
4261- // Get the length of the property and some attributes
4262- // bytes_left will contain the length of the selection
4263- result = XGetWindowProperty (disp, selOwner, pty,
4264- 0, 0, // offset - len
4265- 0, // Delete 0==FALSE
4266- AnyPropertyType,//flag
4267- &type, // return type
4268- &format, // return format
4269- &len, &bytes_left,
4270- &data);
4271- if(result != Success)
4272- debug_printf("clipboard_get: XGetWindowProperty failed! result: %d type:%lu len:%lu format:%d bytes_left:%lu\n",
4273- result, type, len, format, bytes_left);
4274- if(result == Success && bytes_left > 0)
4275- {
4276- result = XGetWindowProperty (disp, selOwner,
4277- pty, 0, bytes_left, 0,
4278- AnyPropertyType, &type, &format,
4279- &len, &dummy, &data);
4280-
4281- if(result == Success)
4282- {
4283- if(type == XA_STRING) //Latin-1: Just copy into low byte of wchar_t
4284- {
4285- wchar_t *ret=(wchar_t *)malloc((bytes_left+1)*sizeof(wchar_t));
4286- std::copy(data, data+bytes_left, ret);
4287- ret[bytes_left]=0;
4288- return ret;
4289- }
4290- // TODO: Handle UTF8 strings
4291- }
4292- else
4293- {
4294- debug_printf("clipboard_get: XGetWindowProperty failed!\n");
4295- return NULL;
4296- }
4297- }
4298- }
4299-
4300- return NULL;
4301-}
4302-
4303-Status sys_clipboard_free(wchar_t *clip_buf)
4304-{
4305- free(clip_buf);
4306- return INFO::OK;
4307-}
4308-
4309-/**
4310- * An SDL Event filter that intercepts other applications' requests for the
4311- * X selection buffer.
4312- *
4313- * @see x11_clipboard_init
4314- * @see sys_clipboard_set
4315- */
4316-int clipboard_filter(void* UNUSED(userdata), SDL_Event* event)
4317-{
4318- /* Pass on all non-window manager specific events immediately */
4319- /* And do nothing if we don't actually have a clip-out to send out */
4320- if(event->type != SDL_SYSWMEVENT || !selection_data)
4321- return 1;
4322-
4323- /* Handle window-manager specific clipboard events */
4324- /* (Note: libsdl must be compiled with X11 support (SDL_VIDEO_DRIVER_X11 in SDL_config.h) -
4325- else you'll get errors like "'struct SDL_SysWMmsg' has no member named 'xevent'") */
4326- XEvent* xevent = &event->syswm.msg->msg.x11.event;
4327- switch(xevent->type) {
4328- /* Copy the selection from our buffer to the requested property, and
4329- convert to the requested target format */
4330- case SelectionRequest: {
4331- XSelectionRequestEvent *req;
4332- XEvent sevent;
4333-
4334- req = &xevent->xselectionrequest;
4335- sevent.xselection.type = SelectionNotify;
4336- sevent.xselection.display = req->display;
4337- sevent.xselection.selection = req->selection;
4338- sevent.xselection.target = req->target;
4339- sevent.xselection.property = None;
4340- sevent.xselection.requestor = req->requestor;
4341- sevent.xselection.time = req->time;
4342- // Simply strip all non-Latin1 characters and replace with '?'
4343- // We should support XA_UTF8
4344- if(req->target == XA_STRING)
4345- {
4346- size_t size = wcslen(selection_data);
4347- u8* buf = (u8*)alloca(size);
4348-
4349- for(size_t i = 0; i < size; i++)
4350- {
4351- buf[i] = selection_data[i] < 0x100 ? selection_data[i] : '?';
4352- }
4353-
4354- XChangeProperty(g_SDL_Display, req->requestor, req->property,
4355- sevent.xselection.target, 8, PropModeReplace,
4356- buf, size);
4357- sevent.xselection.property = req->property;
4358- }
4359- // TODO Add more target formats
4360- XSendEvent(g_SDL_Display, req->requestor, False, 0, &sevent);
4361- XSync(g_SDL_Display, False);
4362- }
4363- break;
4364- }
4365-
4366- return 1;
4367-}
4368-
4369-/**
4370- * Initialization for X clipboard handling, called on-demand by
4371- * sys_clipboard_set.
4372- */
4373-Status x11_clipboard_init()
4374-{
4375- SDL_SysWMinfo info;
4376-
4377- if(get_wminfo(info))
4378- {
4379- /* Save the information for later use */
4380- if(info.subsystem == SDL_SYSWM_X11)
4381- {
4382- g_SDL_Display = info.info.x11.display;
4383- g_SDL_Window = info.info.x11.window;
4384-
4385- /* Enable the special window hook events */
4386- SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
4387- SDL_SetEventFilter(clipboard_filter, NULL);
4388-
4389- return INFO::OK;
4390- }
4391- else
4392- {
4393- return ERR::FAIL;
4394- }
4395- }
4396-
4397- return INFO::OK;
4398-}
4399-
4400-/**
4401- * Set the Selection (i.e. "copy")
4402- *
4403- * Step-by-step (X11)
4404- * <ul>
4405- * <li>Store the selection text in a local buffer
4406- * <li>Tell the X server that we want to own the selection
4407- * <li>Listen for Selection events and respond to them as appropriate
4408- * </ul>
4409- */
4410-Status sys_clipboard_set(const wchar_t *str)
4411-{
4412- ONCE(x11_clipboard_init());
4413-
4414- if(selection_data)
4415- {
4416- free(selection_data);
4417- selection_data = NULL;
4418- }
4419-
4420- selection_size = (wcslen(str)+1)*sizeof(wchar_t);
4421- selection_data = (wchar_t *)malloc(selection_size);
4422- wcscpy(selection_data, str);
4423-
4424- // Like for the clipboard_get code above, we rather use CLIPBOARD than
4425- // PRIMARY - more windows'y behaviour there.
4426- Atom clipboard_atom = XInternAtom(g_SDL_Display, "CLIPBOARD", False);
4427- XSetSelectionOwner(g_SDL_Display, clipboard_atom, g_SDL_Window, CurrentTime);
4428- XSetSelectionOwner(g_SDL_Display, XA_PRIMARY, g_SDL_Window, CurrentTime);
4429-
4430- // SDL2 doesn't have a lockable event thread, so it just uses
4431- // XSync directly instead of lock_func/unlock_func
4432- XSync(g_SDL_Display, False);
4433-
4434- return INFO::OK;
4435-}
4436-
4437-struct sys_cursor_impl
4438-{
4439- XcursorImage* image;
4440- X__Cursor cursor;
4441-};
4442-
4443-static XcursorPixel cursor_pixel_to_x11_format(const XcursorPixel& bgra_pixel)
4444-{
4445- BOOST_STATIC_ASSERT(sizeof(XcursorPixel) == 4 * sizeof(u8));
4446- XcursorPixel ret;
4447- u8* dst = reinterpret_cast<u8*>(&ret);
4448- const u8* b = reinterpret_cast<const u8*>(&bgra_pixel);
4449- const u8 a = b[3];
4450-
4451- for(size_t i = 0; i < 3; ++i)
4452- *dst++ = (b[i]) * a / 255;
4453- *dst = a;
4454- return ret;
4455-}
4456-
4457-Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
4458-{
4459- debug_printf("sys_cursor_create: using Xcursor to create %d x %d cursor\n", w, h);
4460- XcursorImage* image = XcursorImageCreate(w, h);
4461- if(!image)
4462- WARN_RETURN(ERR::FAIL);
4463-
4464- const XcursorPixel* bgra_img_begin = reinterpret_cast<XcursorPixel*>(bgra_img);
4465- std::transform(bgra_img_begin, bgra_img_begin + (w*h), image->pixels,
4466- cursor_pixel_to_x11_format);
4467- image->xhot = hx;
4468- image->yhot = hy;
4469-
4470- SDL_SysWMinfo wminfo;
4471- if(!get_wminfo(wminfo))
4472- WARN_RETURN(ERR::FAIL);
4473-
4474- sys_cursor_impl* impl = new sys_cursor_impl;
4475- impl->image = image;
4476- impl->cursor = XcursorImageLoadCursor(wminfo.info.x11.display, image);
4477- if(impl->cursor == None)
4478- WARN_RETURN(ERR::FAIL);
4479-
4480- *cursor = static_cast<sys_cursor>(impl);
4481- return INFO::OK;
4482-}
4483-
4484-// returns a dummy value representing an empty cursor
4485-Status sys_cursor_create_empty(sys_cursor* cursor)
4486-{
4487- static u8 transparent_bgra[] = { 0x0, 0x0, 0x0, 0x0 };
4488-
4489- return sys_cursor_create(1, 1, static_cast<void*>(transparent_bgra), 0, 0, cursor);
4490-}
4491-
4492-// replaces the current system cursor with the one indicated. need only be
4493-// called once per cursor; pass 0 to restore the default.
4494-Status sys_cursor_set(sys_cursor cursor)
4495-{
4496- if(!cursor) // restore default cursor
4497- SDL_ShowCursor(SDL_DISABLE);
4498- else
4499- {
4500- SDL_SysWMinfo wminfo;
4501- if(!get_wminfo(wminfo))
4502- WARN_RETURN(ERR::FAIL);
4503-
4504- if(wminfo.subsystem != SDL_SYSWM_X11)
4505- WARN_RETURN(ERR::FAIL);
4506-
4507- SDL_ShowCursor(SDL_ENABLE);
4508-
4509- Window window;
4510- if(wminfo.info.x11.window)
4511- window = wminfo.info.x11.window;
4512- else
4513- WARN_RETURN(ERR::FAIL);
4514-
4515- XDefineCursor(wminfo.info.x11.display, window,
4516- static_cast<sys_cursor_impl*>(cursor)->cursor);
4517- // SDL2 doesn't have a lockable event thread, so it just uses
4518- // XSync directly instead of lock_func/unlock_func
4519- XSync(wminfo.info.x11.display, False);
4520- }
4521-
4522- return INFO::OK;}
4523-
4524-// destroys the indicated cursor and frees its resources. if it is
4525-// currently the system cursor, the default cursor is restored first.
4526-Status sys_cursor_free(sys_cursor cursor)
4527-{
4528- // bail now to prevent potential confusion below; there's nothing to do.
4529- if(!cursor)
4530- return INFO::OK;
4531-
4532- sys_cursor_set(0); // restore default cursor
4533- sys_cursor_impl* impl = static_cast<sys_cursor_impl*>(cursor);
4534-
4535- XcursorImageDestroy(impl->image);
4536-
4537- SDL_SysWMinfo wminfo;
4538- if(!get_wminfo(wminfo))
4539- return ERR::FAIL;
4540- XFreeCursor(wminfo.info.x11.display, impl->cursor);
4541-
4542- delete impl;
4543-
4544- return INFO::OK;
4545-}
4546-
4547-Status sys_cursor_reset()
4548-{
4549- return INFO::OK;
4550-}
4551-
4552-
4553-#endif // #if HAVE_X
4554
4555Property changes on: source/lib/sysdep/os/unix/x/x.cpp
4556___________________________________________________________________
4557Deleted: svn:eol-style
4558## -1 +0,0 ##
4559-native
4560\ No newline at end of property
4561Index: source/lib/sysdep/os/win/wclipboard.cpp
4562===================================================================
4563--- source/lib/sysdep/os/win/wclipboard.cpp (revision 23275)
4564+++ source/lib/sysdep/os/win/wclipboard.cpp (nonexistent)
4565@@ -1,123 +0,0 @@
4566-/* Copyright (C) 2010 Wildfire Games.
4567- *
4568- * Permission is hereby granted, free of charge, to any person obtaining
4569- * a copy of this software and associated documentation files (the
4570- * "Software"), to deal in the Software without restriction, including
4571- * without limitation the rights to use, copy, modify, merge, publish,
4572- * distribute, sublicense, and/or sell copies of the Software, and to
4573- * permit persons to whom the Software is furnished to do so, subject to
4574- * the following conditions:
4575- *
4576- * The above copyright notice and this permission notice shall be included
4577- * in all copies or substantial portions of the Software.
4578- *
4579- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4580- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4581- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4582- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4583- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4584- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4585- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4586- */
4587-
4588-#include "precompiled.h"
4589-#include "lib/sysdep/clipboard.h"
4590-
4591-#include "lib/sysdep/os/win/win.h"
4592-#include "lib/sysdep/os/win/wutil.h"
4593-
4594-
4595-// caller is responsible for freeing hMem.
4596-static Status SetClipboardText(const wchar_t* text, HGLOBAL& hMem)
4597-{
4598- const size_t numChars = wcslen(text);
4599- hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, (numChars + 1) * sizeof(wchar_t));
4600- if(!hMem)
4601- WARN_RETURN(ERR::NO_MEM);
4602-
4603- wchar_t* lockedText = (wchar_t*)GlobalLock(hMem);
4604- if(!lockedText)
4605- WARN_RETURN(ERR::NO_MEM);
4606- wcscpy_s(lockedText, numChars+1, text);
4607- GlobalUnlock(hMem);
4608-
4609- HANDLE hData = SetClipboardData(CF_UNICODETEXT, hMem);
4610- if(!hData) // failed
4611- WARN_RETURN(ERR::FAIL);
4612-
4613- return INFO::OK;
4614-}
4615-
4616-
4617-// @return INFO::OK iff text has been assigned a pointer (which the
4618-// caller must free via sys_clipboard_free) to the clipboard text.
4619-static Status GetClipboardText(wchar_t*& text)
4620-{
4621- // NB: Windows NT/2000+ auto convert CF_UNICODETEXT <-> CF_TEXT.
4622-
4623- if(!IsClipboardFormatAvailable(CF_UNICODETEXT))
4624- return INFO::CANNOT_HANDLE;
4625-
4626- HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT);
4627- if(!hMem)
4628- WARN_RETURN(ERR::FAIL);
4629-
4630- const wchar_t* lockedText = (const wchar_t*)GlobalLock(hMem);
4631- if(!lockedText)
4632- WARN_RETURN(ERR::NO_MEM);
4633-
4634- const size_t size = GlobalSize(hMem);
4635- text = (wchar_t*)malloc(size);
4636- if(!text)
4637- WARN_RETURN(ERR::NO_MEM);
4638- wcscpy_s(text, size/sizeof(wchar_t), lockedText);
4639-
4640- (void)GlobalUnlock(hMem);
4641-
4642- return INFO::OK;
4643-}
4644-
4645-
4646-// OpenClipboard parameter.
4647-// NB: using wutil_AppWindow() causes GlobalLock to fail.
4648-static const HWND hWndNewOwner = 0; // MSDN: associate with "current task"
4649-
4650-Status sys_clipboard_set(const wchar_t* text)
4651-{
4652- if(!OpenClipboard(hWndNewOwner))
4653- WARN_RETURN(ERR::FAIL);
4654-
4655- WARN_IF_FALSE(EmptyClipboard());
4656-
4657- // NB: to enable copy/pasting something other than text, add
4658- // message handlers for WM_RENDERFORMAT and WM_RENDERALLFORMATS.
4659- HGLOBAL hMem;
4660- Status ret = SetClipboardText(text, hMem);
4661-
4662- WARN_IF_FALSE(CloseClipboard()); // must happen before GlobalFree
4663-
4664- ENSURE(GlobalFree(hMem) == 0); // (0 indicates success)
4665-
4666- return ret;
4667-}
4668-
4669-
4670-wchar_t* sys_clipboard_get()
4671-{
4672- if(!OpenClipboard(hWndNewOwner))
4673- return 0;
4674-
4675- wchar_t* text;
4676- Status ret = GetClipboardText(text);
4677-
4678- WARN_IF_FALSE(CloseClipboard());
4679-
4680- return (ret == INFO::OK)? text : 0;
4681-}
4682-
4683-
4684-Status sys_clipboard_free(wchar_t* text)
4685-{
4686- free(text);
4687- return INFO::OK;
4688-}
4689
4690Property changes on: source/lib/sysdep/os/win/wclipboard.cpp
4691___________________________________________________________________
4692Deleted: svn:eol-style
4693## -1 +0,0 ##
4694-native
4695\ No newline at end of property
4696Index: source/lib/sysdep/os/win/wcursor.cpp
4697===================================================================
4698--- source/lib/sysdep/os/win/wcursor.cpp (revision 23275)
4699+++ source/lib/sysdep/os/win/wcursor.cpp (nonexistent)
4700@@ -1,142 +0,0 @@
4701-/* Copyright (C) 2018 Wildfire Games.
4702- *
4703- * Permission is hereby granted, free of charge, to any person obtaining
4704- * a copy of this software and associated documentation files (the
4705- * "Software"), to deal in the Software without restriction, including
4706- * without limitation the rights to use, copy, modify, merge, publish,
4707- * distribute, sublicense, and/or sell copies of the Software, and to
4708- * permit persons to whom the Software is furnished to do so, subject to
4709- * the following conditions:
4710- *
4711- * The above copyright notice and this permission notice shall be included
4712- * in all copies or substantial portions of the Software.
4713- *
4714- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4715- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4716- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4717- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4718- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4719- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4720- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4721- */
4722-
4723-#include "precompiled.h"
4724-#include "lib/sysdep/cursor.h"
4725-
4726-#include "lib/sysdep/gfx.h"
4727-#include "lib/sysdep/os/win/win.h"
4728-#include "lib/sysdep/os/win/wutil.h"
4729-
4730-static sys_cursor cursor_from_HICON(HICON hIcon)
4731-{
4732- return (sys_cursor)(uintptr_t)hIcon;
4733-}
4734-
4735-static sys_cursor cursor_from_HCURSOR(HCURSOR hCursor)
4736-{
4737- return (sys_cursor)(uintptr_t)hCursor;
4738-}
4739-
4740-static HICON HICON_from_cursor(sys_cursor cursor)
4741-{
4742- return (HICON)(uintptr_t)cursor;
4743-}
4744-
4745-static HCURSOR HCURSOR_from_cursor(sys_cursor cursor)
4746-{
4747- return (HCURSOR)(uintptr_t)cursor;
4748-}
4749-
4750-
4751-static Status sys_cursor_create_common(int w, int h, void* bgra_img, void* mask_img, int hx, int hy, sys_cursor* cursor)
4752-{
4753- *cursor = 0;
4754-
4755- // MSDN says selecting this HBITMAP into a DC is slower since we use
4756- // CreateBitmap; bpp/format must be checked against those of the DC.
4757- // this is the simplest way and we don't care about slight performance
4758- // differences because this is typically only called once.
4759- HBITMAP hbmColor = CreateBitmap(w, h, 1, 32, bgra_img);
4760-
4761- // CreateIconIndirect doesn't access this; we just need to pass
4762- // an empty bitmap.
4763- HBITMAP hbmMask = CreateBitmap(w, h, 1, 1, mask_img);
4764-
4765- // create the cursor (really an icon; they differ only in
4766- // fIcon and the hotspot definitions).
4767- ICONINFO ii;
4768- ii.fIcon = FALSE; // cursor
4769- ii.xHotspot = (DWORD)hx;
4770- ii.yHotspot = (DWORD)hy;
4771- ii.hbmMask = hbmMask;
4772- ii.hbmColor = hbmColor;
4773- HICON hIcon = CreateIconIndirect(&ii);
4774-
4775- // CreateIconIndirect makes copies, so we no longer need these.
4776- DeleteObject(hbmMask);
4777- DeleteObject(hbmColor);
4778-
4779- if(!wutil_IsValidHandle(hIcon))
4780- WARN_RETURN(ERR::FAIL);
4781-
4782- *cursor = cursor_from_HICON(hIcon);
4783- return INFO::OK;
4784-}
4785-
4786-Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
4787-{
4788- // alpha-blended cursors do not work on a 16-bit display
4789- // (they get drawn as a black square), so refuse to load the
4790- // cursor in that case
4791- int bpp = 0;
4792- RETURN_STATUS_IF_ERR(gfx::GetVideoMode(NULL, NULL, &bpp, NULL));
4793- if (bpp <= 16)
4794- return ERR::FAIL;
4795-
4796- return sys_cursor_create_common(w, h, bgra_img, NULL, hx, hy, cursor);
4797-}
4798-
4799-Status sys_cursor_create_empty(sys_cursor* cursor)
4800-{
4801- // the mask gets ignored on 32-bit displays, but is used on 16-bit displays;
4802- // setting it to 0xFF makes the cursor invisible (though I'm not quite
4803- // sure why it's that way round)
4804- u8 bgra_img[] = {0, 0, 0, 0};
4805- u8 mask_img[] = {0xFF};
4806- return sys_cursor_create_common(1, 1, bgra_img, mask_img, 0, 0, cursor);
4807-}
4808-
4809-
4810-Status sys_cursor_set(sys_cursor cursor)
4811-{
4812- // restore default cursor.
4813- if(!cursor)
4814- cursor = cursor_from_HCURSOR(LoadCursor(0, IDC_ARROW));
4815-
4816- (void)SetCursor(HCURSOR_from_cursor(cursor));
4817- // return value (previous cursor) is useless.
4818-
4819- return INFO::OK;
4820-}
4821-
4822-
4823-Status sys_cursor_free(sys_cursor cursor)
4824-{
4825- // bail now to prevent potential confusion below; there's nothing to do.
4826- if(!cursor)
4827- return INFO::OK;
4828-
4829- // if the cursor being freed is active, restore the default arrow
4830- // (just for safety).
4831- if(cursor_from_HCURSOR(GetCursor()) == cursor)
4832- WARN_IF_ERR(sys_cursor_set(0));
4833-
4834- if(!DestroyIcon(HICON_from_cursor(cursor)))
4835- WARN_RETURN(StatusFromWin());
4836- return INFO::OK;
4837-}
4838-
4839-Status sys_cursor_reset()
4840-{
4841- return INFO::OK;
4842-}
4843
4844Property changes on: source/lib/sysdep/os/win/wcursor.cpp
4845___________________________________________________________________
4846Deleted: svn:eol-style
4847## -1 +0,0 ##
4848-native
4849\ No newline at end of property
4850Index: source/lib/sysdep/os/win/wgfx.cpp
4851===================================================================
4852--- source/lib/sysdep/os/win/wgfx.cpp (revision 23275)
4853+++ source/lib/sysdep/os/win/wgfx.cpp (working copy)
4854@@ -1,187 +1,145 @@
4855 /* Copyright (C) 2017 Wildfire Games.
4856 *
4857 * Permission is hereby granted, free of charge, to any person obtaining
4858 * a copy of this software and associated documentation files (the
4859 * "Software"), to deal in the Software without restriction, including
4860 * without limitation the rights to use, copy, modify, merge, publish,
4861 * distribute, sublicense, and/or sell copies of the Software, and to
4862 * permit persons to whom the Software is furnished to do so, subject to
4863 * the following conditions:
4864 *
4865 * The above copyright notice and this permission notice shall be included
4866 * in all copies or substantial portions of the Software.
4867 *
4868 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4869 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4870 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4871 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4872 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4873 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4874 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4875 */
4876
4877 /*
4878 * graphics card detection on Windows.
4879 */
4880
4881 #include "precompiled.h"
4882 #include "lib/sysdep/os/win/wgfx.h"
4883
4884 #include "lib/sysdep/gfx.h"
4885 #include "lib/sysdep/os/win/wdll_ver.h"
4886 #include "lib/sysdep/os/win/wutil.h"
4887
4888 #if MSC_VERSION
4889 #pragma comment(lib, "advapi32.lib") // registry
4890 #endif
4891
4892
4893 // note: this implementation doesn't require OpenGL to be initialized.
4894 static Status AppendDriverVersionsFromRegistry(VersionList& versionList)
4895 {
4896 // rationale:
4897 // - we could easily determine the 2d driver via EnumDisplaySettings,
4898 // but we want to query the actual OpenGL driver. see
4899 // http://www.opengl.org/discussion_boards/ubb/Forum3/HTML/009679.html ;
4900 // in short, we need the exact OpenGL driver version because some
4901 // driver packs (e.g. Omega) mix and match DLLs.
4902 // - an alternative implementation would be to enumerate all
4903 // DLLs loaded into the process, and check for a glBegin export.
4904 // that requires toolhelp/PSAPI (a bit complicated) and telling
4905 // ICD/opengl32.dll apart (not future-proof).
4906 // - therefore, we stick with the OpenGLDrivers approach. since there is
4907 // no good way to determine which of the subkeys (e.g. nvoglnt) is
4908 // active (several may exist due to previously removed drivers),
4909 // we just display all of them. it is obvious from looking at
4910 // gfx_card which one is correct; we thus avoid driver-specific
4911 // name checks and reporting incorrectly.
4912
4913 wchar_t dllName[MAX_PATH+1];
4914
4915 HKEY hkDrivers;
4916 const wchar_t* key = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\OpenGLDrivers";
4917 // (we've received word of this failing on one WinXP system, but
4918 // AppendDriverVersionsFromKnownFiles might still work.)
4919 if(RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hkDrivers) != 0)
4920 return ERR::FAIL; // NOWARN (see above)
4921
4922 // for each subkey (i.e. installed OpenGL driver):
4923 for(DWORD i = 0; ; i++)
4924 {
4925 wchar_t driverName[32];
4926 DWORD driverNameLength = ARRAY_SIZE(driverName);
4927 const LONG err = RegEnumKeyExW(hkDrivers, i, driverName, &driverNameLength, 0, 0, 0, 0);
4928 if(err == ERROR_NO_MORE_ITEMS)
4929 {
4930 if(i == 0)
4931 {
4932 RegCloseKey(hkDrivers);
4933 return ERR::NOT_SUPPORTED; // NOWARN (ATI and NVidia don't create sub-keys on Windows 7)
4934 }
4935 break;
4936 }
4937 ENSURE(err == ERROR_SUCCESS);
4938
4939 HKEY hkDriver;
4940 if(RegOpenKeyExW(hkDrivers, driverName, 0, KEY_QUERY_VALUE, &hkDriver) == 0)
4941 {
4942 DWORD dllNameLength = ARRAY_SIZE(dllName)-5; // for ".dll"
4943 if(RegQueryValueExW(hkDriver, L"Dll", 0, 0, (LPBYTE)dllName, &dllNameLength) == 0)
4944 wdll_ver_Append(dllName, versionList);
4945
4946 RegCloseKey(hkDriver);
4947 }
4948 }
4949
4950 // for each value:
4951 // (some old drivers, e.g. S3 Super Savage, store their ICD name in a
4952 // single REG_SZ value. we therefore include those as well.)
4953 for(DWORD i = 0; ; i++)
4954 {
4955 wchar_t name[100]; // we don't need this, but RegEnumValue fails otherwise.
4956 DWORD nameLength = ARRAY_SIZE(name);
4957 DWORD type;
4958 DWORD dllNameLength = ARRAY_SIZE(dllName)-5; // for ".dll"
4959 const DWORD err = RegEnumValueW(hkDrivers, i, name, &nameLength, 0, &type, (LPBYTE)dllName, &dllNameLength);
4960 if(err == ERROR_NO_MORE_ITEMS)
4961 break;
4962 ENSURE(err == ERROR_SUCCESS);
4963 if(type == REG_SZ)
4964 wdll_ver_Append(dllName, versionList);
4965 }
4966
4967 RegCloseKey(hkDrivers);
4968
4969 return INFO::OK;
4970 }
4971
4972
4973 static void AppendDriverVersionsFromKnownFiles(VersionList& versionList)
4974 {
4975 // (check all known file names regardless of gfx_card, which may change and
4976 // defeat our parsing. this takes about 5..10 ms)
4977
4978 // NVidia
4979 wdll_ver_Append(L"nvoglv64.dll", versionList);
4980 wdll_ver_Append(L"nvoglv32.dll", versionList);
4981 wdll_ver_Append(L"nvoglnt.dll", versionList);
4982
4983 // ATI
4984 wdll_ver_Append(L"atioglxx.dll", versionList);
4985
4986 // Intel
4987 wdll_ver_Append(L"ig4icd32.dll", versionList);
4988 wdll_ver_Append(L"ig4icd64.dll", versionList);
4989 wdll_ver_Append(L"iglicd32.dll", versionList);
4990 }
4991
4992
4993 std::wstring wgfx_DriverInfo()
4994 {
4995 VersionList versionList;
4996 if(AppendDriverVersionsFromRegistry(versionList) != INFO::OK) // (fails on Windows 7)
4997 AppendDriverVersionsFromKnownFiles(versionList);
4998 return versionList;
4999 }
5000-
5001-
5002-//-----------------------------------------------------------------------------
5003-// direct implementations of some gfx functions
5004-
5005-namespace gfx {
5006-
5007-Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
5008-{
5009- DEVMODE dm = { sizeof(dm) };
5010-
5011- if(!EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm))
5012- WARN_RETURN(ERR::FAIL);
5013-
5014- // EnumDisplaySettings is documented to set the values of the following:
5015- const DWORD expectedFlags = DM_PELSWIDTH|DM_PELSHEIGHT|DM_BITSPERPEL|DM_DISPLAYFREQUENCY|DM_DISPLAYFLAGS;
5016- ENSURE((dm.dmFields & expectedFlags) == expectedFlags);
5017-
5018- if(xres)
5019- *xres = (int)dm.dmPelsWidth;
5020- if(yres)
5021- *yres = (int)dm.dmPelsHeight;
5022- if(bpp)
5023- *bpp = (int)dm.dmBitsPerPel;
5024- if(freq)
5025- *freq = (int)dm.dmDisplayFrequency;
5026-
5027- return INFO::OK;
5028-}
5029-
5030-
5031-Status GetMonitorSize(int& width_mm, int& height_mm)
5032-{
5033- // (DC for the primary monitor's entire screen)
5034- const HDC hDC = GetDC(0);
5035- width_mm = GetDeviceCaps(hDC, HORZSIZE);
5036- height_mm = GetDeviceCaps(hDC, VERTSIZE);
5037- ReleaseDC(0, hDC);
5038- return INFO::OK;
5039-}
5040-
5041-} // namespace gfx
5042Index: source/lib/sysdep/os/win/wsysdep.cpp
5043===================================================================
5044--- source/lib/sysdep/os/win/wsysdep.cpp (revision 23275)
5045+++ source/lib/sysdep/os/win/wsysdep.cpp (working copy)
5046@@ -1,612 +1,603 @@
5047 /* Copyright (C) 2011 Wildfire Games.
5048 *
5049 * Permission is hereby granted, free of charge, to any person obtaining
5050 * a copy of this software and associated documentation files (the
5051 * "Software"), to deal in the Software without restriction, including
5052 * without limitation the rights to use, copy, modify, merge, publish,
5053 * distribute, sublicense, and/or sell copies of the Software, and to
5054 * permit persons to whom the Software is furnished to do so, subject to
5055 * the following conditions:
5056 *
5057 * The above copyright notice and this permission notice shall be included
5058 * in all copies or substantial portions of the Software.
5059 *
5060 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
5061 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
5062 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
5063 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
5064 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
5065 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
5066 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5067 */
5068
5069 /*
5070 * Windows backend of the sysdep interface
5071 */
5072
5073 #include "precompiled.h"
5074 #include "lib/sysdep/sysdep.h"
5075
5076 #include "lib/alignment.h"
5077 #include "lib/sysdep/os/win/win.h" // includes windows.h; must come before shlobj
5078 #include <shlobj.h> // pick_dir
5079 #include <shellapi.h> // open_url
5080 #include <Wincrypt.h>
5081 #include <WindowsX.h> // message crackers
5082 #include <winhttp.h>
5083
5084-#include "lib/sysdep/clipboard.h"
5085 #include "lib/sysdep/os/win/error_dialog.h"
5086 #include "lib/sysdep/os/win/wutil.h"
5087
5088 #if CONFIG_ENABLE_BOOST
5089 # include <boost/algorithm/string.hpp>
5090 #endif
5091
5092
5093 #if MSC_VERSION
5094 #pragma comment(lib, "shell32.lib") // for sys_pick_directory SH* calls
5095 #pragma comment(lib, "winhttp.lib")
5096 #endif
5097
5098
5099 bool sys_IsDebuggerPresent()
5100 {
5101 return (IsDebuggerPresent() != 0);
5102 }
5103
5104
5105 std::wstring sys_WideFromArgv(const char* argv_i)
5106 {
5107 // NB: despite http://cbloomrants.blogspot.com/2008/06/06-14-08-1.html,
5108 // WinXP x64 EN cmd.exe (chcp reports 437) encodes argv u-umlaut
5109 // (entered manually or via auto-complete) via cp1252. the same applies
5110 // to WinXP SP2 DE (where chcp reports 850).
5111 const UINT cp = CP_ACP;
5112 const DWORD flags = MB_PRECOMPOSED|MB_ERR_INVALID_CHARS;
5113 const int inputSize = -1; // null-terminated
5114 std::vector<wchar_t> buf(strlen(argv_i)+1); // (upper bound on number of characters)
5115 // NB: avoid mbstowcs because it may specify another locale
5116 const int ret = MultiByteToWideChar(cp, flags, argv_i, (int)inputSize, &buf[0], (int)buf.size());
5117 ENSURE(ret != 0);
5118 return std::wstring(&buf[0]);
5119 }
5120
5121
5122 void sys_display_msg(const wchar_t* caption, const wchar_t* msg)
5123 {
5124 MessageBoxW(0, msg, caption, MB_ICONEXCLAMATION|MB_TASKMODAL|MB_SETFOREGROUND);
5125 }
5126
5127
5128 //-----------------------------------------------------------------------------
5129 // "program error" dialog (triggered by ENSURE and exception)
5130 //-----------------------------------------------------------------------------
5131
5132 // support for resizing the dialog / its controls (must be done manually)
5133
5134 static POINTS dlg_clientOrigin;
5135 static POINTS dlg_prevClientSize;
5136
5137 static void dlg_OnMove(HWND UNUSED(hDlg), int x, int y)
5138 {
5139 dlg_clientOrigin.x = (short)x;
5140 dlg_clientOrigin.y = (short)y;
5141 }
5142
5143
5144 static const size_t ANCHOR_LEFT = 0x01;
5145 static const size_t ANCHOR_RIGHT = 0x02;
5146 static const size_t ANCHOR_TOP = 0x04;
5147 static const size_t ANCHOR_BOTTOM = 0x08;
5148 static const size_t ANCHOR_ALL = 0x0F;
5149
5150 static void dlg_ResizeControl(HWND hDlg, int dlgItem, int dx, int dy, size_t anchors)
5151 {
5152 HWND hControl = GetDlgItem(hDlg, dlgItem);
5153 RECT r;
5154 GetWindowRect(hControl, &r);
5155
5156 int w = r.right - r.left, h = r.bottom - r.top;
5157 int x = r.left - dlg_clientOrigin.x, y = r.top - dlg_clientOrigin.y;
5158
5159 if(anchors & ANCHOR_RIGHT)
5160 {
5161 // right only
5162 if(!(anchors & ANCHOR_LEFT))
5163 x += dx;
5164 // horizontal (stretch width)
5165 else
5166 w += dx;
5167 }
5168
5169 if(anchors & ANCHOR_BOTTOM)
5170 {
5171 // bottom only
5172 if(!(anchors & ANCHOR_TOP))
5173 y += dy;
5174 // vertical (stretch height)
5175 else
5176 h += dy;
5177 }
5178
5179 SetWindowPos(hControl, 0, x,y, w,h, SWP_NOZORDER);
5180 }
5181
5182
5183 static void dlg_OnSize(HWND hDlg, UINT state, int clientSizeX, int clientSizeY)
5184 {
5185 // 'minimize' was clicked. we need to ignore this, otherwise
5186 // dx/dy would reduce some control positions to less than 0.
5187 // since Windows clips them, we wouldn't later be able to
5188 // reconstruct the previous values when 'restoring'.
5189 if(state == SIZE_MINIMIZED)
5190 return;
5191
5192 // NB: origin might legitimately be 0, but we know it is invalid
5193 // on the first call to this function, where dlg_prevClientSize is 0.
5194 const bool isOriginValid = (dlg_prevClientSize.y != 0);
5195
5196 const int dx = clientSizeX - dlg_prevClientSize.x;
5197 const int dy = clientSizeY - dlg_prevClientSize.y;
5198 dlg_prevClientSize.x = (short)clientSizeX;
5199 dlg_prevClientSize.y = (short)clientSizeY;
5200
5201 if(!isOriginValid) // must not call dlg_ResizeControl
5202 return;
5203
5204 dlg_ResizeControl(hDlg, IDC_CONTINUE, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
5205 dlg_ResizeControl(hDlg, IDC_SUPPRESS, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
5206 dlg_ResizeControl(hDlg, IDC_BREAK , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
5207 dlg_ResizeControl(hDlg, IDC_EXIT , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
5208 dlg_ResizeControl(hDlg, IDC_COPY , dx,dy, ANCHOR_RIGHT|ANCHOR_BOTTOM);
5209 dlg_ResizeControl(hDlg, IDC_EDIT1 , dx,dy, ANCHOR_ALL);
5210 }
5211
5212
5213 static void dlg_OnGetMinMaxInfo(HWND UNUSED(hDlg), LPMINMAXINFO mmi)
5214 {
5215 // we must make sure resize_control will never set negative coords -
5216 // Windows would clip them, and its real position would be lost.
5217 // restrict to a reasonable and good looking minimum size [pixels].
5218 mmi->ptMinTrackSize.x = 407;
5219 mmi->ptMinTrackSize.y = 159; // determined experimentally
5220 }
5221
5222
5223 struct DialogParams
5224 {
5225 const wchar_t* text;
5226 size_t flags;
5227 };
5228
5229 static BOOL dlg_OnInitDialog(HWND hDlg, HWND UNUSED(hWndFocus), LPARAM lParam)
5230 {
5231 const DialogParams* params = (const DialogParams*)lParam;
5232 HWND hWnd;
5233
5234 // need to reset for new instance of dialog
5235 dlg_clientOrigin.x = dlg_clientOrigin.y = 0;
5236 dlg_prevClientSize.x = dlg_prevClientSize.y = 0;
5237
5238 if(!(params->flags & DE_ALLOW_SUPPRESS))
5239 {
5240 hWnd = GetDlgItem(hDlg, IDC_SUPPRESS);
5241 EnableWindow(hWnd, FALSE);
5242 }
5243
5244 // set fixed font for readability
5245 hWnd = GetDlgItem(hDlg, IDC_EDIT1);
5246 HGDIOBJ hObj = (HGDIOBJ)GetStockObject(SYSTEM_FIXED_FONT);
5247 LPARAM redraw = FALSE;
5248 SendMessage(hWnd, WM_SETFONT, (WPARAM)hObj, redraw);
5249
5250 SetDlgItemTextW(hDlg, IDC_EDIT1, params->text);
5251 return TRUE; // set default keyboard focus
5252 }
5253
5254
5255 static void dlg_OnCommand(HWND hDlg, int id, HWND UNUSED(hWndCtl), UINT UNUSED(codeNotify))
5256 {
5257 switch(id)
5258 {
5259- case IDC_COPY:
5260- {
5261- std::vector<wchar_t> buf(128*KiB); // (too big for stack)
5262- GetDlgItemTextW(hDlg, IDC_EDIT1, &buf[0], (int)buf.size());
5263- sys_clipboard_set(&buf[0]);
5264- break;
5265- }
5266-
5267 case IDC_CONTINUE:
5268 EndDialog(hDlg, ERI_CONTINUE);
5269 break;
5270 case IDC_SUPPRESS:
5271 EndDialog(hDlg, ERI_SUPPRESS);
5272 break;
5273 case IDC_BREAK:
5274 EndDialog(hDlg, ERI_BREAK);
5275 break;
5276 case IDC_EXIT:
5277 EndDialog(hDlg, ERI_EXIT);
5278 break;
5279
5280 default:
5281 break;
5282 }
5283 }
5284
5285
5286 static void dlg_OnSysCommand(HWND hDlg, UINT cmd, int UNUSED(x), int UNUSED(y))
5287 {
5288 switch(cmd & 0xFFF0) // NB: lower 4 bits are reserved
5289 {
5290 // [X] clicked -> close dialog (doesn't happen automatically)
5291 case SC_CLOSE:
5292 EndDialog(hDlg, 0);
5293 break;
5294
5295 default:
5296 break;
5297 }
5298 }
5299
5300
5301 static INT_PTR CALLBACK dlg_OnMessage(HWND hDlg, unsigned int msg, WPARAM wParam, LPARAM lParam)
5302 {
5303 switch(msg)
5304 {
5305 case WM_INITDIALOG:
5306 return HANDLE_WM_INITDIALOG(hDlg, wParam, lParam, dlg_OnInitDialog);
5307
5308 case WM_SYSCOMMAND:
5309 return HANDLE_WM_SYSCOMMAND(hDlg, wParam, lParam, dlg_OnSysCommand);
5310
5311 case WM_COMMAND:
5312 return HANDLE_WM_COMMAND(hDlg, wParam, lParam, dlg_OnCommand);
5313
5314 case WM_MOVE:
5315 return HANDLE_WM_MOVE(hDlg, wParam, lParam, dlg_OnMove);
5316
5317 case WM_GETMINMAXINFO:
5318 return HANDLE_WM_GETMINMAXINFO(hDlg, wParam, lParam, dlg_OnGetMinMaxInfo);
5319
5320 case WM_SIZE:
5321 return HANDLE_WM_SIZE(hDlg, wParam, lParam, dlg_OnSize);
5322
5323 default:
5324 // we didn't process the message; caller will perform default action.
5325 return FALSE;
5326 }
5327 }
5328
5329
5330 ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags)
5331 {
5332 // note: other threads might still be running, crash and take down the
5333 // process before we have a chance to display this error message.
5334 // ideally we would suspend them all and resume when finished; however,
5335 // they may be holding system-wide locks (e.g. heap or loader) that
5336 // are potentially needed by DialogBoxParam. in that case, deadlock
5337 // would result; this is much worse than a crash because no error
5338 // at all is displayed to the end-user. therefore, do nothing here.
5339
5340 // temporarily remove any pending quit message from the queue because
5341 // it would prevent the dialog from being displayed (DialogBoxParam
5342 // returns IDOK without doing anything). will be restored below.
5343 // notes:
5344 // - this isn't only relevant at exit - Windows also posts one if
5345 // window init fails. therefore, it is important that errors can be
5346 // displayed regardless.
5347 // - by passing hWnd=0, we check all windows belonging to the current
5348 // thread. there is no reason to use hWndParent below.
5349 MSG msg;
5350 const BOOL isQuitPending = PeekMessage(&msg, 0, WM_QUIT, WM_QUIT, PM_REMOVE);
5351
5352 const HINSTANCE hInstance = wutil_LibModuleHandle();
5353 LPCWSTR lpTemplateName = MAKEINTRESOURCEW(IDD_DIALOG1);
5354 const DialogParams params = { text, flags };
5355 // get the enclosing app's window handle. we can't just pass 0 or
5356 // the desktop window because the dialog must be modal (if the app
5357 // continues running, it may crash and take down the process before
5358 // we've managed to show the dialog).
5359 const HWND hWndParent = wutil_AppWindow();
5360
5361 INT_PTR ret = DialogBoxParamW(hInstance, lpTemplateName, hWndParent, dlg_OnMessage, (LPARAM)¶ms);
5362
5363 if(isQuitPending)
5364 PostQuitMessage((int)msg.wParam);
5365
5366 // failed; warn user and make sure we return an ErrorReactionInternal.
5367 if(ret == 0 || ret == -1)
5368 {
5369 debug_DisplayMessage(L"Error", L"Unable to display detailed error dialog.");
5370 return ERI_CONTINUE;
5371 }
5372 return (ErrorReactionInternal)ret;
5373 }
5374
5375
5376 //-----------------------------------------------------------------------------
5377 // misc
5378 //-----------------------------------------------------------------------------
5379
5380 Status sys_StatusDescription(int user_err, wchar_t* buf, size_t max_chars)
5381 {
5382 // validate user_err - Win32 doesn't have negative error numbers
5383 if(user_err < 0)
5384 return ERR::FAIL; // NOWARN
5385
5386 const DWORD err = user_err? (DWORD)user_err : GetLastError();
5387
5388 // no one likes to see "The operation completed successfully" in
5389 // error messages, so return more descriptive text instead.
5390 if(err == 0)
5391 {
5392 wcscpy_s(buf, max_chars, L"0 (no error code was set)");
5393 return INFO::OK;
5394 }
5395
5396 wchar_t message[400];
5397 {
5398 const LPCVOID source = 0; // ignored (we're not using FROM_HMODULE etc.)
5399 const DWORD lang_id = 0; // look for neutral, then current locale
5400 va_list* args = 0; // we don't care about "inserts"
5401 const DWORD charsWritten = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, source, err, lang_id, message, (DWORD)ARRAY_SIZE(message), args);
5402 if(!charsWritten)
5403 WARN_RETURN(ERR::FAIL);
5404 ENSURE(charsWritten < max_chars);
5405 if(message[charsWritten-1] == '\n')
5406 message[charsWritten-1] = '\0';
5407 if(message[charsWritten-2] == '\r')
5408 message[charsWritten-2] = '\0';
5409 }
5410
5411 const int charsWritten = swprintf_s(buf, max_chars, L"%d (%ls)", err, message);
5412 ENSURE(charsWritten != -1);
5413 return INFO::OK;
5414 }
5415
5416
5417 static Status GetModulePathname(HMODULE hModule, OsPath& pathname)
5418 {
5419 wchar_t pathnameBuf[32768]; // NTFS limit
5420 const DWORD length = (DWORD)ARRAY_SIZE(pathnameBuf);
5421 const DWORD charsWritten = GetModuleFileNameW(hModule, pathnameBuf, length);
5422 if(charsWritten == 0) // failed
5423 WARN_RETURN(StatusFromWin());
5424 ENSURE(charsWritten < length); // why would the above buffer ever be exceeded?
5425 pathname = pathnameBuf;
5426 return INFO::OK;
5427 }
5428
5429
5430 Status sys_get_module_filename(void* addr, OsPath& pathname)
5431 {
5432 MEMORY_BASIC_INFORMATION mbi;
5433 const SIZE_T bytesWritten = VirtualQuery(addr, &mbi, sizeof(mbi));
5434 if(!bytesWritten)
5435 WARN_RETURN(StatusFromWin());
5436 ENSURE(bytesWritten >= sizeof(mbi));
5437 return GetModulePathname((HMODULE)mbi.AllocationBase, pathname);
5438 }
5439
5440
5441 OsPath sys_ExecutablePathname()
5442 {
5443 WinScopedPreserveLastError s;
5444 OsPath pathname;
5445 ENSURE(GetModulePathname(0, pathname) == INFO::OK);
5446 return pathname;
5447 }
5448
5449
5450 std::wstring sys_get_user_name()
5451 {
5452 wchar_t usernameBuf[256];
5453 DWORD size = ARRAY_SIZE(usernameBuf);
5454 if(!GetUserNameW(usernameBuf, &size))
5455 return L"";
5456 return usernameBuf;
5457 }
5458
5459
5460 // callback for shell directory picker: used to set starting directory
5461 // (for user convenience).
5462 static int CALLBACK BrowseCallback(HWND hWnd, unsigned int msg, LPARAM UNUSED(lParam), LPARAM lpData)
5463 {
5464 if(msg == BFFM_INITIALIZED)
5465 {
5466 const WPARAM wParam = TRUE; // lpData is a Unicode string, not PIDL.
5467 // (MSDN: the return values for both of these BFFM_ notifications are ignored)
5468 (void)SendMessage(hWnd, BFFM_SETSELECTIONW, wParam, lpData);
5469 }
5470
5471 return 0;
5472 }
5473
5474 Status sys_pick_directory(OsPath& path)
5475 {
5476 // (must not use multi-threaded apartment due to BIF_NEWDIALOGSTYLE)
5477 const HRESULT hr = CoInitialize(0);
5478 ENSURE(hr == S_OK || hr == S_FALSE); // S_FALSE == already initialized
5479
5480 // note: bi.pszDisplayName isn't the full path, so it isn't of any use.
5481 BROWSEINFOW bi;
5482 memset(&bi, 0, sizeof(bi));
5483 bi.ulFlags = BIF_RETURNONLYFSDIRS|BIF_NEWDIALOGSTYLE|BIF_NONEWFOLDERBUTTON;
5484 // for setting starting directory:
5485 bi.lpfn = (BFFCALLBACK)BrowseCallback;
5486 const Path::String initialPath = OsString(path); // NB: BFFM_SETSELECTIONW can't deal with '/' separators
5487 bi.lParam = (LPARAM)initialPath.c_str();
5488 const LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
5489 if(!pidl) // user canceled
5490 return INFO::SKIPPED;
5491
5492 // translate ITEMIDLIST to string
5493 wchar_t pathBuf[MAX_PATH]; // mandated by SHGetPathFromIDListW
5494 const BOOL ok = SHGetPathFromIDListW(pidl, pathBuf);
5495
5496 // free the ITEMIDLIST
5497 CoTaskMemFree(pidl);
5498
5499 if(ok == TRUE)
5500 {
5501 path = pathBuf;
5502 return INFO::OK;
5503 }
5504
5505 // Balance call to CoInitialize, which must have been successful
5506 CoUninitialize();
5507
5508 WARN_RETURN(StatusFromWin());
5509 }
5510
5511
5512 Status sys_open_url(const std::string& url)
5513 {
5514 HINSTANCE r = ShellExecuteA(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL);
5515 if ((int)(intptr_t)r > 32)
5516 return INFO::OK;
5517
5518 WARN_RETURN(ERR::FAIL);
5519 }
5520
5521
5522 Status sys_generate_random_bytes(u8* buffer, size_t size)
5523 {
5524 HCRYPTPROV hCryptProv = 0;
5525 if(!CryptAcquireContext(&hCryptProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
5526 WARN_RETURN(StatusFromWin());
5527
5528 memset(buffer, 0, size);
5529 if(!CryptGenRandom(hCryptProv, (DWORD)size, (BYTE*)buffer))
5530 WARN_RETURN(StatusFromWin());
5531
5532 if(!CryptReleaseContext(hCryptProv, 0))
5533 WARN_RETURN(StatusFromWin());
5534
5535 return INFO::OK;
5536 }
5537
5538
5539 #if CONFIG_ENABLE_BOOST
5540
5541 /*
5542 * Given a string of the form
5543 * "example.com:80"
5544 * or
5545 * "ftp=ftp.example.com:80;http=example.com:80;https=example.com:80"
5546 * separated by semicolons or whitespace,
5547 * return the string "example.com:80".
5548 */
5549 static std::wstring parse_proxy(const std::wstring& input)
5550 {
5551 if(input.find('=') == input.npos)
5552 return input;
5553
5554 std::vector<std::wstring> parts;
5555 split(parts, input, boost::algorithm::is_any_of("; \t\r\n"), boost::algorithm::token_compress_on);
5556
5557 for(size_t i = 0; i < parts.size(); ++i)
5558 if(boost::algorithm::starts_with(parts[i], "http="))
5559 return parts[i].substr(5);
5560
5561 // If we got this far, proxies were only set for non-HTTP protocols
5562 return L"";
5563 }
5564
5565 Status sys_get_proxy_config(const std::wstring& url, std::wstring& proxy)
5566 {
5567 WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
5568 memset(&autoProxyOptions, 0, sizeof(autoProxyOptions));
5569 autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
5570 autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
5571 autoProxyOptions.fAutoLogonIfChallenged = TRUE;
5572
5573 WINHTTP_PROXY_INFO proxyInfo;
5574 memset(&proxyInfo, 0, sizeof(proxyInfo));
5575
5576 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieConfig;
5577 memset(&ieConfig, 0, sizeof(ieConfig));
5578
5579 HINTERNET hSession = NULL;
5580
5581 Status err = INFO::SKIPPED;
5582
5583 bool useAutoDetect;
5584
5585 if(WinHttpGetIEProxyConfigForCurrentUser(&ieConfig))
5586 {
5587 if(ieConfig.lpszAutoConfigUrl)
5588 {
5589 // Use explicit auto-config script if specified
5590 useAutoDetect = true;
5591 autoProxyOptions.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL;
5592 autoProxyOptions.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrl;
5593 }
5594 else
5595 {
5596 // Use auto-discovery if enabled
5597 useAutoDetect = (ieConfig.fAutoDetect == TRUE);
5598 }
5599 }
5600 else
5601 {
5602 // Can't find IE config settings - fall back to auto-discovery
5603 useAutoDetect = true;
5604 }
5605
5606 if(useAutoDetect)
5607 {
5608 hSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
5609 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
5610
5611 if(hSession && WinHttpGetProxyForUrl(hSession, url.c_str(), &autoProxyOptions, &proxyInfo) && proxyInfo.lpszProxy)
5612 {
5613 proxy = parse_proxy(proxyInfo.lpszProxy);
5614 if(!proxy.empty())
5615 {
5616 err = INFO::OK;
5617 goto done;
5618 }
5619 }
5620 }
5621
5622 // No valid auto-config; try explicit proxy instead
5623 if(ieConfig.lpszProxy)
5624 {
5625 proxy = parse_proxy(ieConfig.lpszProxy);
5626 if(!proxy.empty())
5627 {
5628 err = INFO::OK;
5629 goto done;
5630 }
5631 }
5632
5633 done:
5634 if(ieConfig.lpszProxy)
5635 GlobalFree(ieConfig.lpszProxy);
5636 if(ieConfig.lpszProxyBypass)
5637 GlobalFree(ieConfig.lpszProxyBypass);
5638 if(ieConfig.lpszAutoConfigUrl)
5639 GlobalFree(ieConfig.lpszAutoConfigUrl);
5640 if(proxyInfo.lpszProxy)
5641 GlobalFree(proxyInfo.lpszProxy);
5642 if(proxyInfo.lpszProxyBypass)
5643 GlobalFree(proxyInfo.lpszProxyBypass);
5644 if(hSession)
5645 WinHttpCloseHandle(hSession);
5646
5647 return err;
5648 }
5649
5650 #endif
5651
5652 FILE* sys_OpenFile(const OsPath& pathname, const char* mode)
5653 {
5654 FILE* f = 0;
5655 const std::wstring wmode(mode, mode+strlen(mode));
5656 (void)_wfopen_s(&f, OsString(pathname).c_str(), wmode.c_str());
5657 return f;
5658 }
5659Index: source/lib/sysdep/os/win/wutil.cpp
5660===================================================================
5661--- source/lib/sysdep/os/win/wutil.cpp (revision 23275)
5662+++ source/lib/sysdep/os/win/wutil.cpp (working copy)
5663@@ -1,580 +1,580 @@
5664 /* Copyright (C) 2015 Wildfire Games.
5665 *
5666 * Permission is hereby granted, free of charge, to any person obtaining
5667 * a copy of this software and associated documentation files (the
5668 * "Software"), to deal in the Software without restriction, including
5669 * without limitation the rights to use, copy, modify, merge, publish,
5670 * distribute, sublicense, and/or sell copies of the Software, and to
5671 * permit persons to whom the Software is furnished to do so, subject to
5672 * the following conditions:
5673 *
5674 * The above copyright notice and this permission notice shall be included
5675 * in all copies or substantial portions of the Software.
5676 *
5677 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
5678 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
5679 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
5680 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
5681 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
5682 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
5683 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5684 */
5685
5686 /*
5687 * various Windows-specific utilities
5688 */
5689
5690 #include "precompiled.h"
5691 #include "lib/sysdep/os/win/wutil.h"
5692
5693 #include <stdio.h>
5694 #include <stdlib.h> // __argc
5695
5696 #include "lib/file/file.h"
5697 #include "lib/posix/posix.h"
5698 #include "lib/sysdep/sysdep.h"
5699 #include "lib/sysdep/os/win/win.h"
5700 #include "lib/sysdep/os/win/wdbg.h" // wdbg_assert
5701 #include "lib/sysdep/os/win/winit.h"
5702
5703 #include <shlobj.h> // SHGetFolderPath
5704
5705
5706 WINIT_REGISTER_EARLY_INIT(wutil_Init);
5707 WINIT_REGISTER_LATE_SHUTDOWN(wutil_Shutdown);
5708
5709
5710 //-----------------------------------------------------------------------------
5711 // safe allocator
5712
5713 // may be used independently of libc malloc
5714 // (in particular, before _cinit and while calling static dtors).
5715 // used by wpthread critical section code.
5716
5717 void* wutil_Allocate(size_t size)
5718 {
5719 const DWORD flags = HEAP_ZERO_MEMORY;
5720 return HeapAlloc(GetProcessHeap(), flags, size);
5721 }
5722
5723 void wutil_Free(void* p)
5724 {
5725 const DWORD flags = 0;
5726 HeapFree(GetProcessHeap(), flags, p);
5727 }
5728
5729
5730 //-----------------------------------------------------------------------------
5731 // locks
5732
5733 // several init functions are before called before _cinit.
5734 // POSIX static mutex init may not have been done by then,
5735 // so we need our own lightweight functions.
5736
5737 static CRITICAL_SECTION cs[NUM_CS];
5738 static bool cs_valid;
5739
5740 void wutil_Lock(WinLockId id)
5741 {
5742 if(!cs_valid)
5743 return;
5744 EnterCriticalSection(&cs[id]);
5745 }
5746
5747 void wutil_Unlock(WinLockId id)
5748 {
5749 if(!cs_valid)
5750 return;
5751 LeaveCriticalSection(&cs[id]);
5752 }
5753
5754 bool wutil_IsLocked(WinLockId id)
5755 {
5756 if(!cs_valid)
5757 return false;
5758 const BOOL successfullyEntered = TryEnterCriticalSection(&cs[id]);
5759 if(!successfullyEntered)
5760 return true; // still locked
5761 LeaveCriticalSection(&cs[id]);
5762 return false; // probably not locked
5763 }
5764
5765
5766 static void InitLocks()
5767 {
5768 for(int i = 0; i < NUM_CS; i++)
5769 InitializeCriticalSection(&cs[i]);
5770
5771 cs_valid = true;
5772 }
5773
5774 static void ShutdownLocks()
5775 {
5776 cs_valid = false;
5777
5778 for(int i = 0; i < NUM_CS; i++)
5779 DeleteCriticalSection(&cs[i]);
5780 memset(cs, 0, sizeof(cs));
5781 }
5782
5783
5784 //-----------------------------------------------------------------------------
5785 // error codes
5786
5787 // only call after a Win32 function indicates failure.
5788 Status StatusFromWin()
5789 {
5790 switch(GetLastError())
5791 {
5792 case ERROR_BUSY:
5793 case WAIT_TIMEOUT:
5794 return ERR::AGAIN;
5795 case ERROR_OPERATION_ABORTED:
5796 return ERR::ABORTED;
5797
5798 case ERROR_INVALID_HANDLE:
5799 return ERR::INVALID_HANDLE;
5800 case ERROR_INSUFFICIENT_BUFFER:
5801 return ERR::INVALID_SIZE;
5802 case ERROR_INVALID_PARAMETER:
5803 case ERROR_BAD_ARGUMENTS:
5804 return ERR::INVALID_PARAM;
5805
5806 case ERROR_OUTOFMEMORY:
5807 case ERROR_NOT_ENOUGH_MEMORY:
5808 return ERR::NO_MEM;
5809 case ERROR_NOT_SUPPORTED:
5810 case ERROR_CALL_NOT_IMPLEMENTED:
5811 case ERROR_PROC_NOT_FOUND:
5812 return ERR::NOT_SUPPORTED;
5813
5814 case ERROR_FILE_NOT_FOUND:
5815 case ERROR_PATH_NOT_FOUND:
5816 return ERR::FILE_NOT_FOUND;
5817 case ERROR_ACCESS_DENIED:
5818 return ERR::FILE_ACCESS;
5819
5820 default:
5821 return ERR::FAIL;
5822 }
5823 }
5824
5825
5826 //-----------------------------------------------------------------------------
5827 // command line
5828
5829 // copy of GetCommandLine string. will be tokenized and then referenced by
5830 // the argv pointers.
5831 static wchar_t* argvContents;
5832
5833 int s_argc = 0;
5834 wchar_t** s_argv = 0;
5835
5836 static void ReadCommandLine()
5837 {
5838 const wchar_t* commandLine = GetCommandLineW();
5839 // (this changes as quotation marks are removed)
5840 size_t numChars = wcslen(commandLine);
5841 argvContents = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, (numChars+1)*sizeof(wchar_t));
5842 wcscpy_s(argvContents, numChars+1, commandLine);
5843
5844 // first pass: tokenize string and count number of arguments
5845 bool ignoreSpace = false;
5846 for(size_t i = 0; i < numChars; i++)
5847 {
5848 switch(argvContents[i])
5849 {
5850 case '"':
5851 ignoreSpace = !ignoreSpace;
5852 // strip the " character
5853 memmove(argvContents+i, argvContents+i+1, (numChars-i)*sizeof(wchar_t));
5854 numChars--;
5855 i--;
5856 break;
5857
5858 case ' ':
5859 if(!ignoreSpace)
5860 {
5861 argvContents[i] = '\0';
5862 s_argc++;
5863 }
5864 break;
5865 }
5866 }
5867 s_argc++;
5868
5869 // have argv entries point into the tokenized string
5870 s_argv = (wchar_t**)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, s_argc*sizeof(wchar_t*));
5871 wchar_t* nextArg = argvContents;
5872 for(int i = 0; i < s_argc; i++)
5873 {
5874 s_argv[i] = nextArg;
5875 nextArg += wcslen(nextArg)+1;
5876 }
5877 }
5878
5879
5880 int wutil_argc()
5881 {
5882 return s_argc;
5883 }
5884
5885 wchar_t** wutil_argv()
5886 {
5887 ENSURE(s_argv);
5888 return s_argv;
5889 }
5890
5891
5892 static void FreeCommandLine()
5893 {
5894 HeapFree(GetProcessHeap(), 0, s_argv);
5895 HeapFree(GetProcessHeap(), 0, argvContents);
5896 }
5897
5898
5899 bool wutil_HasCommandLineArgument(const wchar_t* arg)
5900 {
5901 for(int i = 0; i < s_argc; i++)
5902 {
5903 if(!wcscmp(s_argv[i], arg))
5904 return true;
5905 }
5906
5907 return false;
5908 }
5909
5910
5911 //-----------------------------------------------------------------------------
5912 // directories
5913
5914 // (NB: wutil_Init is called before static ctors => use placement new)
5915 static OsPath* systemPath;
5916 static OsPath* executablePath;
5917 static OsPath* localAppdataPath;
5918 static OsPath* roamingAppdataPath;
5919 static OsPath* personalPath;
5920
5921 const OsPath& wutil_SystemPath()
5922 {
5923 return *systemPath;
5924 }
5925
5926 const OsPath& wutil_ExecutablePath()
5927 {
5928 return *executablePath;
5929 }
5930
5931 const OsPath& wutil_LocalAppdataPath()
5932 {
5933 return *localAppdataPath;
5934 }
5935
5936 const OsPath& wutil_RoamingAppdataPath()
5937 {
5938 return *roamingAppdataPath;
5939 }
5940
5941 const OsPath& wutil_PersonalPath()
5942 {
5943 return *personalPath;
5944 }
5945
5946 // Helper to avoid duplicating this setup
5947 static OsPath* GetFolderPath(int csidl)
5948 {
5949 HWND hwnd = 0; // ignored unless a dial-up connection is needed to access the folder
5950 HANDLE token = 0;
5951 wchar_t path[MAX_PATH]; // mandated by SHGetFolderPathW
5952 const HRESULT ret = SHGetFolderPathW(hwnd, csidl, token, 0, path);
5953 if (!SUCCEEDED(ret))
5954 {
5955 debug_printf("SHGetFolderPathW failed with HRESULT = 0x%08lx for csidl = 0x%04x\n", ret, csidl);
5956 debug_warn("SHGetFolderPathW failed (see debug output)");
5957 }
5958 if(GetLastError() == ERROR_NO_TOKEN) // avoid polluting last error
5959 SetLastError(0);
5960 return new(wutil_Allocate(sizeof(OsPath))) OsPath(path);
5961 }
5962
5963 static void GetDirectories()
5964 {
5965 WinScopedPreserveLastError s;
5966
5967 // system directory
5968 {
5969 const UINT length = GetSystemDirectoryW(0, 0);
5970 ENSURE(length != 0);
5971 std::wstring path(length, '\0');
5972 const UINT charsWritten = GetSystemDirectoryW(&path[0], length);
5973 ENSURE(charsWritten == length-1);
5974 systemPath = new(wutil_Allocate(sizeof(OsPath))) OsPath(path);
5975 }
5976
5977 // executable's directory
5978 executablePath = new(wutil_Allocate(sizeof(OsPath))) OsPath(sys_ExecutablePathname().Parent());
5979
5980 // roaming application data
5981 roamingAppdataPath = GetFolderPath(CSIDL_APPDATA);
5982
5983 // local application data
5984 localAppdataPath = GetFolderPath(CSIDL_LOCAL_APPDATA);
5985
5986 // my documents
5987 personalPath = GetFolderPath(CSIDL_PERSONAL);
5988 }
5989
5990
5991 static void FreeDirectories()
5992 {
5993 systemPath->~OsPath();
5994 wutil_Free(systemPath);
5995 executablePath->~OsPath();
5996 wutil_Free(executablePath);
5997 localAppdataPath->~OsPath();
5998 wutil_Free(localAppdataPath);
5999 roamingAppdataPath->~OsPath();
6000 wutil_Free(roamingAppdataPath);
6001 personalPath->~OsPath();
6002 wutil_Free(personalPath);
6003 }
6004
6005
6006 //-----------------------------------------------------------------------------
6007 // user32 fix
6008
6009 // HACK: make sure a reference to user32 is held, even if someone
6010 // decides to delay-load it. this fixes bug #66, which was the
6011 // Win32 mouse cursor (set via user32!SetCursor) appearing as a
6012 // black 32x32(?) rectangle. the underlying cause was as follows:
6013 // powrprof.dll was the first client of user32, causing it to be
6014 // loaded. after we were finished with powrprof, we freed it, in turn
6015 // causing user32 to unload. later code would then reload user32,
6016 // which apparently terminally confused the cursor implementation.
6017 //
6018 // since we hold a reference here, user32 will never unload.
6019 // of course, the benefits of delay-loading are lost for this DLL,
6020 // but that is unavoidable. it is safer to force loading it, rather
6021 // than documenting the problem and asking it not be delay-loaded.
6022 static HMODULE hUser32Dll;
6023
6024 static void ForciblyLoadUser32Dll()
6025 {
6026 hUser32Dll = LoadLibraryW(L"user32.dll");
6027 }
6028
6029 // avoids Boundschecker warning
6030 static void FreeUser32Dll()
6031 {
6032 FreeLibrary(hUser32Dll);
6033 }
6034
6035
6036 //-----------------------------------------------------------------------------
6037 // memory
6038
6039 static void EnableLowFragmentationHeap()
6040 {
6041 if(IsDebuggerPresent())
6042 {
6043 // and the debug heap isn't explicitly disabled,
6044 char* var = getenv("_NO_DEBUG_HEAP");
6045 if(!var || var[0] != '1')
6046 return; // we can't enable the LFH
6047 }
6048
6049 #if WINVER >= 0x0501
6050 WUTIL_FUNC(pHeapSetInformation, BOOL, (HANDLE, HEAP_INFORMATION_CLASS, void*, size_t));
6051 WUTIL_IMPORT_KERNEL32(HeapSetInformation, pHeapSetInformation);
6052 if(pHeapSetInformation)
6053 {
6054 ULONG flags = 2; // enable LFH
6055 pHeapSetInformation(GetProcessHeap(), HeapCompatibilityInformation, &flags, sizeof(flags));
6056 }
6057 #endif // #if WINVER >= 0x0501
6058 }
6059
6060
6061 //-----------------------------------------------------------------------------
6062 // Wow64
6063
6064 // Wow64 'helpfully' redirects all 32-bit apps' accesses of
6065 // %windir%\\system32\\drivers to %windir%\\system32\\drivers\\SysWOW64.
6066 // that's bad, because the actual drivers are not in the subdirectory. to
6067 // work around this, provide for temporarily disabling redirection.
6068
6069 static WUTIL_FUNC(pIsWow64Process, BOOL, (HANDLE, PBOOL));
6070 static WUTIL_FUNC(pWow64DisableWow64FsRedirection, BOOL, (PVOID*));
6071 static WUTIL_FUNC(pWow64RevertWow64FsRedirection, BOOL, (PVOID));
6072
6073 static bool isWow64;
6074
6075 static void ImportWow64Functions()
6076 {
6077 WUTIL_IMPORT_KERNEL32(IsWow64Process, pIsWow64Process);
6078 WUTIL_IMPORT_KERNEL32(Wow64DisableWow64FsRedirection, pWow64DisableWow64FsRedirection);
6079 WUTIL_IMPORT_KERNEL32(Wow64RevertWow64FsRedirection, pWow64RevertWow64FsRedirection);
6080 }
6081
6082 static void DetectWow64()
6083 {
6084 // function not found => running on 32-bit Windows
6085 if(!pIsWow64Process)
6086 {
6087 isWow64 = false;
6088 return;
6089 }
6090
6091 BOOL isWow64Process = FALSE;
6092 const BOOL ok = pIsWow64Process(GetCurrentProcess(), &isWow64Process);
6093 WARN_IF_FALSE(ok);
6094 isWow64 = (isWow64Process == TRUE);
6095 }
6096
6097 bool wutil_IsWow64()
6098 {
6099 return isWow64;
6100 }
6101
6102
6103 WinScopedDisableWow64Redirection::WinScopedDisableWow64Redirection()
6104 {
6105 // note: don't just check if the function pointers are valid. 32-bit
6106 // Vista includes them but isn't running Wow64, so calling the functions
6107 // would fail. since we have to check if actually on Wow64, there's no
6108 // more need to verify the pointers (their existence is implied).
6109 if(!wutil_IsWow64())
6110 return;
6111 const BOOL ok = pWow64DisableWow64FsRedirection(&m_wasRedirectionEnabled);
6112 WARN_IF_FALSE(ok);
6113 }
6114
6115 WinScopedDisableWow64Redirection::~WinScopedDisableWow64Redirection()
6116 {
6117 if(!wutil_IsWow64())
6118 return;
6119 const BOOL ok = pWow64RevertWow64FsRedirection(m_wasRedirectionEnabled);
6120 WARN_IF_FALSE(ok);
6121 }
6122
6123
6124 //-----------------------------------------------------------------------------
6125
6126 Status wutil_SetPrivilege(const wchar_t* privilege, bool enable)
6127 {
6128 WinScopedPreserveLastError s;
6129
6130 HANDLE hToken;
6131 if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken))
6132 return ERR::_1;
6133
6134 TOKEN_PRIVILEGES tp;
6135 if (!LookupPrivilegeValueW(NULL, privilege, &tp.Privileges[0].Luid))
6136 return ERR::_2;
6137 tp.PrivilegeCount = 1;
6138 tp.Privileges[0].Attributes = enable? SE_PRIVILEGE_ENABLED : 0;
6139
6140 SetLastError(0);
6141 const BOOL ok = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, 0, 0);
6142 if(!ok || GetLastError() != 0)
6143 return ERR::_3;
6144
6145 WARN_IF_FALSE(CloseHandle(hToken));
6146 return INFO::OK;
6147 }
6148
6149
6150 //-----------------------------------------------------------------------------
6151 // module handle
6152
6153 #ifndef LIB_STATIC_LINK
6154
6155 #include "lib/sysdep/os/win/wdll_main.h"
6156
6157 HMODULE wutil_LibModuleHandle()
6158 {
6159 HMODULE hModule;
6160 const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
6161- const BOOL ok = GetModuleHandleEx(flags, (LPCWSTR)&wutil_LibModuleHandle, &hModule);
6162+ const BOOL ok = GetModuleHandleEx(flags, (LPCSTR)&wutil_LibModuleHandle, &hModule);
6163 // (avoid ENSURE etc. because we're called from debug_DisplayError)
6164 wdbg_assert(ok);
6165 return hModule;
6166 }
6167
6168 #else
6169
6170 HMODULE wutil_LibModuleHandle()
6171 {
6172 return GetModuleHandle(0);
6173 }
6174
6175 #endif
6176
6177
6178
6179 //-----------------------------------------------------------------------------
6180 // find main window
6181
6182 // this is required by the error dialog and clipboard code.
6183 // note that calling from wutil_Init won't work, because the app will not
6184 // have created its window by then.
6185
6186 static HWND hAppWindow;
6187
6188 static BOOL CALLBACK FindAppWindowByPid(HWND hWnd, LPARAM UNUSED(lParam))
6189 {
6190 DWORD pid;
6191 DWORD tid = GetWindowThreadProcessId(hWnd, &pid);
6192 UNUSED2(tid);
6193
6194 if(pid == GetCurrentProcessId())
6195 hAppWindow = hWnd;
6196
6197 return TRUE; // keep calling
6198 }
6199
6200 HWND wutil_AppWindow()
6201 {
6202 if(!hAppWindow)
6203 {
6204 WARN_IF_FALSE(EnumWindows(FindAppWindowByPid, 0));
6205 // (hAppWindow may still be 0 if we haven't created a window yet)
6206 }
6207
6208 return hAppWindow;
6209 }
6210
6211
6212 //-----------------------------------------------------------------------------
6213
6214 static Status wutil_Init()
6215 {
6216 InitLocks();
6217
6218 ForciblyLoadUser32Dll();
6219
6220 EnableLowFragmentationHeap();
6221
6222 ReadCommandLine();
6223
6224 GetDirectories();
6225
6226 ImportWow64Functions();
6227 DetectWow64();
6228
6229 return INFO::OK;
6230 }
6231
6232
6233 static Status wutil_Shutdown()
6234 {
6235 FreeCommandLine();
6236
6237 FreeUser32Dll();
6238
6239 ShutdownLocks();
6240
6241 FreeDirectories();
6242
6243 return INFO::OK;
6244 }
6245Index: source/meson.build
6246===================================================================
6247--- source/meson.build (nonexistent)
6248+++ source/meson.build (working copy)
6249@@ -0,0 +1,1034 @@
6250+cpp_args = []
6251+if host_machine.system() == 'linux'
6252+ cpp_args += '-DLINUX'
6253+endif
6254+
6255+if host_machine.system() == 'windows'
6256+ cpp_args += '-DUSING_PCH=1'
6257+endif
6258+
6259+if get_option('buildtype') == 'release'
6260+ cpp_args += '-DNDEBUG'
6261+else
6262+ cpp_args += '-D_DEBUG'
6263+endif
6264+
6265+if get_option('gles')
6266+ cpp_args += '-DCONFIG2_GLES=1'
6267+else
6268+ cpp_args += '-DCONFIG2_GLES=0'
6269+endif
6270+
6271+if get_option('audio')
6272+ cpp_args += '-DCONFIG2_AUDIO=1'
6273+else
6274+ cpp_args += '-DCONFIG2_AUDIO=0'
6275+endif
6276+
6277+if get_option('lobby')
6278+ cpp_args += '-DCONFIG2_LOBBY=1'
6279+else
6280+ cpp_args += '-DCONFIG2_LOBBY=0'
6281+endif
6282+
6283+if get_option('upnp')
6284+ cpp_args += '-DCONFIG2_MINIUPNPC=1'
6285+else
6286+ cpp_args += '-DCONFIG2_MINIUPNPC=0'
6287+endif
6288+
6289+if get_option('nvtt')
6290+ cpp_args += '-DCONFIG2_NVTT=1'
6291+else
6292+ cpp_args += '-DCONFIG2_NVTT=0'
6293+endif
6294+
6295+collada_args = cpp_args
6296+collada_args += '-DHAVE_ICONV_CONST=1 -DICONV_CONST=const -DLIBICONV_STATIC'
6297+collada_sources = [
6298+ 'collada/precompiled.cpp',
6299+ 'collada/CommonConvert.cpp',
6300+ 'collada/Decompose.cpp',
6301+ 'collada/DLL.cpp',
6302+ 'collada/GeomReindex.cpp',
6303+ 'collada/Maths.cpp',
6304+ 'collada/PMDConvert.cpp',
6305+ 'collada/PSAConvert.cpp',
6306+ 'collada/StdSkeletons.cpp',
6307+ 'collada/XMLFix.cpp',
6308+]
6309+collada_dependencies = [dep_fcollada, dep_libxml2, dep_iconv]
6310+collada = library(
6311+ 'Collada',
6312+ sources: collada_sources,
6313+ cpp_pch: 'collada/precompiled.h',
6314+ dependencies: collada_dependencies,
6315+ cpp_args: collada_args,
6316+)
6317+
6318+glooxwrapper_cpp_args = cpp_args
6319+glooxwrapper_cpp_args += '-DLIB_STATIC_LINK=1'
6320+glooxwrapper_sources = [
6321+ 'lobby/glooxwrapper/glooxwrapper.cpp',
6322+]
6323+glooxwrapper_dependencies = [dep_gloox, dep_boost]
6324+glooxwrapper = library(
6325+ 'glooxwrapper',
6326+ glooxwrapper_sources,
6327+ include_directories: [
6328+ 'pch/glooxwrapper',
6329+ ],
6330+ dependencies: glooxwrapper_dependencies,
6331+ cpp_args: glooxwrapper_cpp_args,
6332+)
6333+
6334+encryption = static_library(
6335+ 'encryption',
6336+ ['third_party/encryption/pkcs5_pbkdf2.cpp'],
6337+ include_directories: 'lib',
6338+ dependencies: [dep_sodium, dep_boost],
6339+ cpp_args: cpp_args,
6340+)
6341+
6342+if get_option('lobby')
6343+
6344+ lobby_sources = [
6345+ 'lobby/scripting/GlooxScriptConversions.cpp',
6346+ 'lobby/Globals.cpp',
6347+ 'lobby/scripting/JSInterface_Lobby.cpp',
6348+ 'lobby/StanzaExtensions.cpp',
6349+ 'lobby/XmppClient.cpp',
6350+ 'pch/lobby/precompiled.cpp',
6351+ ]
6352+ lobby_cpp_args = cpp_args
6353+ lobby_cpp_args += '-DLIB_STATIC_LINK=1'
6354+ lobby = static_library(
6355+ 'lobby',
6356+ lobby_sources,
6357+ include_directories: 'lib',
6358+ dependencies: [dep_boost, dep_js, dep_gloox, dep_tinygettext, dep_sodium, dep_icu, dep_iconv, dep_enet],
6359+ cpp_args: lobby_cpp_args,
6360+ link_with: [glooxwrapper, encryption],
6361+ )
6362+else
6363+ lobby_sources = [
6364+ 'lobby/Globals.cpp',
6365+ 'lobby/scripting/JSInterface_Lobby.cpp',
6366+ ]
6367+
6368+ lobby = static_library(
6369+ 'lobby',
6370+ lobby_sources,
6371+ include_directories: 'lib',
6372+ dependencies: [dep_js],
6373+ cpp_args: cpp_args,
6374+ )
6375+endif
6376+
6377+network_cpp_args = cpp_args
6378+network_cpp_args += '-DLIB_STATIC_LINK=1'
6379+network_sources = [
6380+ 'network/fsm.cpp',
6381+ 'network/NetClient.cpp',
6382+ 'network/NetClientTurnManager.cpp',
6383+ 'network/NetFileTransfer.cpp',
6384+ 'network/NetHost.cpp',
6385+ 'network/NetMessage.cpp',
6386+ 'network/NetMessageSim.cpp',
6387+ 'network/NetServer.cpp',
6388+ 'network/NetServerTurnManager.cpp',
6389+ 'network/NetSession.cpp',
6390+ 'network/NetStats.cpp',
6391+ 'network/StunClient.cpp',
6392+ 'network/scripting/JSInterface_Network.cpp',
6393+]
6394+
6395+network_dependencies = [dep_boost,dep_enet, dep_js, dep_sdl2]
6396+
6397+if get_option('upnp')
6398+ network_dependencies += dep_miniupnpc # Not supported without
6399+endif
6400+
6401+network = static_library(
6402+ 'network',
6403+ network_sources,
6404+ include_directories: 'pch/network/',
6405+ dependencies: network_dependencies,
6406+ cpp_args: network_cpp_args,
6407+)
6408+
6409+tinygettext_cpp_args = cpp_args
6410+tinygettext_cpp_args += '-DLIB_STATIC_LINK=1'
6411+# TODO: make sure we don’t want to use the normal gettext instead.
6412+tinygettext_sources = [
6413+ 'third_party/tinygettext/src/dictionary.cpp',
6414+ 'third_party/tinygettext/src/dictionary_manager.cpp',
6415+ 'third_party/tinygettext/src/iconv.cpp',
6416+ 'third_party/tinygettext/src/language.cpp',
6417+ 'third_party/tinygettext/src/log.cpp',
6418+ 'third_party/tinygettext/src/plural_forms.cpp',
6419+ 'third_party/tinygettext/src/po_parser.cpp',
6420+ 'third_party/tinygettext/src/tinygettext.cpp',
6421+ 'third_party/tinygettext/src/unix_file_system.cpp',
6422+]
6423+
6424+tinygettext = static_library(
6425+ 'tinygettext',
6426+ tinygettext_sources,
6427+ include_directories: 'pch/tinygettext/',
6428+ dependencies: [dep_tinygettext, dep_boost, dep_iconv],
6429+ cpp_args: tinygettext_cpp_args,
6430+)
6431+
6432+lowlevel_sources = []
6433+
6434+if get_option('audio')
6435+ lowlevel_sources += 'lib/snd.cpp'
6436+endif
6437+
6438+lowlevel_sources += [
6439+ 'lib/allocators/arena.cpp',
6440+ 'lib/allocators/dynarray.cpp',
6441+ 'lib/allocators/freelist.cpp',
6442+ 'lib/allocators/headerless.cpp',
6443+ 'lib/allocators/page_aligned.cpp',
6444+ 'lib/allocators/pool.cpp',
6445+ 'lib/allocators/shared_ptr.cpp',
6446+ 'lib/allocators/unique_range.cpp',
6447+ 'lib/app_hooks.cpp',
6448+ 'lib/base32.cpp',
6449+ 'lib/bits.cpp',
6450+ 'lib/byte_order.cpp',
6451+ 'lib/debug.cpp',
6452+ 'lib/debug_stl.cpp',
6453+ 'lib/external_libraries/dbghelp.cpp',
6454+ 'lib/file/archive/archive.cpp',
6455+ 'lib/file/archive/archive_zip.cpp',
6456+ 'lib/file/archive/codec.cpp',
6457+ 'lib/file/archive/codec_zlib.cpp',
6458+ 'lib/file/archive/stream.cpp',
6459+ 'lib/file/common/file_loader.cpp',
6460+ 'lib/file/common/file_stats.cpp',
6461+ 'lib/file/common/real_directory.cpp',
6462+ 'lib/file/common/trace.cpp',
6463+ 'lib/file/file.cpp',
6464+ 'lib/file/file_system.cpp',
6465+ 'lib/file/io/io.cpp',
6466+ 'lib/file/io/write_buffer.cpp',
6467+ 'lib/file/vfs/vfs.cpp',
6468+ 'lib/file/vfs/vfs_lookup.cpp',
6469+ 'lib/file/vfs/vfs_path.cpp',
6470+ 'lib/file/vfs/vfs_populate.cpp',
6471+ 'lib/file/vfs/vfs_tree.cpp',
6472+ 'lib/file/vfs/vfs_util.cpp',
6473+ 'lib/fnv_hash.cpp',
6474+ 'lib/frequency_filter.cpp',
6475+ 'lib/input.cpp',
6476+ 'lib/lib.cpp',
6477+ 'lib/module_init.cpp',
6478+ 'lib/ogl.cpp',
6479+ 'lib/path.cpp',
6480+ 'lib/posix/posix.cpp',
6481+ 'lib/rand.cpp',
6482+ 'lib/regex.cpp',
6483+ 'lib/res/graphics/cursor.cpp',
6484+ 'lib/res/graphics/ogl_tex.cpp',
6485+ 'lib/res/h_mgr.cpp',
6486+ 'lib/secure_crt.cpp',
6487+ 'lib/status.cpp',
6488+ 'lib/svn_revision.cpp',
6489+
6490+ # TODO: system specific files, to sort.
6491+ 'lib/sysdep/cpu.cpp',
6492+ 'lib/sysdep/gfx.cpp',
6493+ 'lib/sysdep/os_cpu.cpp',
6494+ 'lib/sysdep/smbios.cpp',
6495+
6496+ 'lib/tex/tex_bmp.cpp',
6497+ 'lib/tex/tex_codec.cpp',
6498+ 'lib/tex/tex.cpp',
6499+ 'lib/tex/tex_dds.cpp',
6500+ 'lib/tex/tex_png.cpp',
6501+ 'lib/tex/tex_tga.cpp',
6502+ 'lib/timer.cpp',
6503+ 'lib/utf8.cpp',
6504+ 'lib/wsecure_crt.cpp',
6505+]
6506+if host_machine.cpu_family() == 'x86'
6507+ lowlevel_sources += [
6508+ 'lib/sysdep/arch/ia32/ia32.cpp',
6509+ ]
6510+ if host_machine.system() == 'windows'
6511+ lowlevel_sources += [
6512+ 'lib/sysdep/arch/amd64/amd64.cpp',
6513+ 'lib/sysdep/arch/x86_x64/apic.cpp',
6514+ 'lib/sysdep/arch/x86_x64/cache.cpp',
6515+ 'lib/sysdep/arch/x86_x64/topology.cpp',
6516+ 'lib/sysdep/arch/x86_x64/x86_x64.cpp',
6517+ 'lib/sysdep/arch/x86_x64/msr.cpp',
6518+ ]
6519+ endif
6520+
6521+elif host_machine.cpu_family() == 'x86_64'
6522+ lowlevel_sources += [
6523+ 'lib/sysdep/arch/amd64/amd64.cpp',
6524+ 'lib/sysdep/arch/x86_x64/apic.cpp',
6525+ 'lib/sysdep/arch/x86_x64/cache.cpp',
6526+ 'lib/sysdep/arch/x86_x64/topology.cpp',
6527+ 'lib/sysdep/arch/x86_x64/x86_x64.cpp',
6528+ ]
6529+elif host_machine.cpu_family() == 'arm'
6530+ lowlevel_sources += [
6531+ 'lib/sysdep/arch/arm/arm.cpp',
6532+ ]
6533+elif host_machine.cpu_family() == 'aarch64'
6534+ lowlevel_sources += [
6535+ 'lib/sysdep/arch/aarch64/aarch64.cpp',
6536+ ]
6537+endif
6538+
6539+if host_machine.system() == 'windows'
6540+ lowlevel_sources += [
6541+ 'lib/sysdep/acpi.cpp',
6542+ 'lib/sysdep/os/win/mahaf.cpp',
6543+ 'lib/sysdep/os/win/manifest.cpp',
6544+ 'lib/sysdep/os/win/wcpu.cpp',
6545+ 'lib/sysdep/os/win/wdbg.cpp',
6546+ 'lib/sysdep/os/win/wdbg_heap.cpp',
6547+ 'lib/sysdep/os/win/wdbg_sym.cpp',
6548+ 'lib/sysdep/os/win/wdir_watch.cpp',
6549+ 'lib/sysdep/os/win/wdll_delay_load.cpp',
6550+ 'lib/sysdep/os/win/wdll_ver.cpp',
6551+ 'lib/sysdep/os/win/wfirmware.cpp',
6552+ 'lib/sysdep/os/win/wgfx.cpp',
6553+ 'lib/sysdep/os/win/whrt/counter.cpp',
6554+ 'lib/sysdep/os/win/whrt/hpet.cpp',
6555+ 'lib/sysdep/os/win/whrt/pmt.cpp',
6556+ 'lib/sysdep/os/win/whrt/qpc.cpp',
6557+ 'lib/sysdep/os/win/whrt/tgt.cpp',
6558+ 'lib/sysdep/os/win/whrt/tsc.cpp',
6559+ 'lib/sysdep/os/win/whrt/whrt.cpp',
6560+ 'lib/sysdep/os/win/winit.cpp',
6561+ 'lib/sysdep/os/win/wiocp.cpp',
6562+ 'lib/sysdep/os/win/wnuma.cpp',
6563+ 'lib/sysdep/os/win/wposix/waio.cpp',
6564+ 'lib/sysdep/os/win/wposix/wdlfcn.cpp',
6565+ 'lib/sysdep/os/win/wposix/wfilesystem.cpp',
6566+ 'lib/sysdep/os/win/wposix/wmman.cpp',
6567+ 'lib/sysdep/os/win/wposix/wposix.cpp',
6568+ 'lib/sysdep/os/win/wposix/wpthread.cpp',
6569+ 'lib/sysdep/os/win/wposix/wtime.cpp',
6570+ 'lib/sysdep/os/win/wposix/wutsname.cpp',
6571+ 'lib/sysdep/os/win/wprofiler.cpp',
6572+ 'lib/sysdep/os/win/wseh.cpp',
6573+ 'lib/sysdep/os/win/wstartup.cpp',
6574+ 'lib/sysdep/os/win/wsysdep.cpp',
6575+ 'lib/sysdep/os/win/wutil.cpp',
6576+ 'lib/sysdep/os/win/wversion.cpp',
6577+ 'lib/sysdep/os/win/wvm.cpp',
6578+ 'lib/sysdep/rtl/msc/msc.cpp',
6579+ ]
6580+elif host_machine.system() == 'darwin'
6581+ lowlevel_sources += [
6582+ 'lib/sysdep/os/osx/dir_watch.cpp',
6583+ 'lib/sysdep/os/osx/ocpu.cpp',
6584+ 'lib/sysdep/os/osx/odbg.cpp',
6585+ 'lib/sysdep/os/osx/osx.cpp',
6586+ ]
6587+elif host_machine.system() == 'linux'
6588+ lowlevel_sources += [
6589+ 'lib/sysdep/os/linux/dir_watch_inotify.cpp',
6590+ 'lib/sysdep/os/linux/lcpu.cpp',
6591+ 'lib/sysdep/os/linux/ldbg.cpp',
6592+ 'lib/sysdep/os/linux/linux.cpp',
6593+ ]
6594+elif host_machine.system() == 'android'
6595+ lowlevel_sources += [
6596+ 'lib/sysdep/os/android/android.cpp',
6597+ ]
6598+elif ['openbsd', 'netbsd', 'freebsd', 'gnu/kfreebsd', 'dragonfly'].contains(host_machine.system())
6599+ lowlevel_sources += [
6600+ 'lib/sysdep/os/bsd/bcpu.cpp',
6601+ 'lib/sysdep/os/bsd/bdbg.cpp',
6602+ 'lib/sysdep/os/bsd/bsd.cpp',
6603+ 'lib/sysdep/os/bsd/dir_watch.cpp',
6604+ ]
6605+endif
6606+
6607+if host_machine.system() != 'windows'
6608+ lowlevel_sources += [
6609+ 'lib/sysdep/os/unix/udbg.cpp',
6610+ 'lib/sysdep/os/unix/ufilesystem.cpp',
6611+ 'lib/sysdep/os/unix/unix.cpp',
6612+ 'lib/sysdep/os/unix/unix_executable_pathname.cpp',
6613+ 'lib/sysdep/os/unix/unuma.cpp',
6614+ 'lib/sysdep/os/unix/uvm.cpp',
6615+ 'lib/sysdep/rtl/gcc/gcc.cpp',
6616+ ]
6617+endif
6618+
6619+
6620+lowlevel_cpp_args = cpp_args
6621+lowlevel_cpp_args += '-DLIB_STATIC_LINK=1'
6622+lowlevel = static_library(
6623+ 'lowlevel',
6624+ lowlevel_sources,
6625+ include_directories: ['lib', 'pch/lowlevel/'],
6626+ dependencies: [dep_boost, dep_sdl2, dep_gl, dep_openal, dep_zlib, dep_png],
6627+ cpp_args: lowlevel_cpp_args,
6628+)
6629+
6630+sound_cpp_args = cpp_args
6631+sound_cpp_args += '-DLIB_STATIC_LINK=1'
6632+
6633+if get_option('audio')
6634+ soundmanager_sources = [
6635+ 'soundmanager/data/ogg.cpp',
6636+ 'soundmanager/data/OggData.cpp',
6637+ 'soundmanager/data/SoundData.cpp',
6638+ 'soundmanager/items/CBufferItem.cpp',
6639+ 'soundmanager/items/CSoundBase.cpp',
6640+ 'soundmanager/items/CSoundItem.cpp',
6641+ 'soundmanager/items/CStreamItem.cpp',
6642+ 'soundmanager/scripting/JSInterface_Sound.cpp',
6643+ 'soundmanager/scripting/SoundGroup.cpp',
6644+ 'soundmanager/SoundManager.cpp',
6645+ ]
6646+
6647+ soundmanager = static_library(
6648+ 'soundmanager',
6649+ soundmanager_sources,
6650+ include_directories: 'lib',
6651+ dependencies: [dep_openal, dep_vorbis, dep_boost, dep_sdl2, dep_js],
6652+ link_with: [lowlevel],
6653+ cpp_args: sound_cpp_args,
6654+ )
6655+
6656+else
6657+ soundmanager_sources = [
6658+ 'soundmanager/SoundManager.cpp',
6659+ 'soundmanager/scripting/JSInterface_Sound.cpp',
6660+ ]
6661+
6662+ soundmanager = static_library(
6663+ 'soundmanager',
6664+ soundmanager_sources,
6665+ include_directories: 'lib',
6666+ dependencies: [dep_sdl2, dep_js],
6667+ link_with: [lowlevel],
6668+ cpp_args: sound_cpp_args,
6669+ )
6670+
6671+endif
6672+gui_sources = [
6673+ 'gui/CGUI.cpp',
6674+ 'gui/CGUIScrollBarVertical.cpp',
6675+ 'gui/CGUISetting.cpp',
6676+ 'gui/CGUISprite.cpp',
6677+ 'gui/CGUIText.cpp',
6678+ 'gui/GUIManager.cpp',
6679+ 'gui/GUIMatrix.cpp',
6680+ 'gui/GUIRenderer.cpp',
6681+ 'gui/GUIStringConversions.cpp',
6682+ 'gui/GUITooltip.cpp',
6683+ 'gui/IGUIScrollBar.cpp',
6684+ 'gui/ObjectBases/IGUIButtonBehavior.cpp',
6685+ 'gui/ObjectBases/IGUIObject.cpp',
6686+ 'gui/ObjectBases/IGUIScrollBarOwner.cpp',
6687+ 'gui/ObjectBases/IGUITextOwner.cpp',
6688+ 'gui/ObjectTypes/CButton.cpp',
6689+ 'gui/ObjectTypes/CChart.cpp',
6690+ 'gui/ObjectTypes/CCheckBox.cpp',
6691+ 'gui/ObjectTypes/CDropDown.cpp',
6692+ 'gui/ObjectTypes/CImage.cpp',
6693+ 'gui/ObjectTypes/CInput.cpp',
6694+ 'gui/ObjectTypes/CList.cpp',
6695+ 'gui/ObjectTypes/CMiniMap.cpp',
6696+ 'gui/ObjectTypes/COList.cpp',
6697+ 'gui/ObjectTypes/CProgressBar.cpp',
6698+ 'gui/ObjectTypes/CRadioButton.cpp',
6699+ 'gui/ObjectTypes/CSlider.cpp',
6700+ 'gui/ObjectTypes/CText.cpp',
6701+ 'gui/ObjectTypes/CTooltip.cpp',
6702+ 'gui/Scripting/GuiScriptConversions.cpp',
6703+ 'gui/Scripting/JSInterface_GUIManager.cpp',
6704+ 'gui/Scripting/JSInterface_GUISize.cpp',
6705+ 'gui/Scripting/JSInterface_IGUIObject.cpp',
6706+ 'gui/Scripting/ScriptFunctions.cpp',
6707+ 'gui/SettingTypes/CGUIColor.cpp',
6708+ 'gui/SettingTypes/CGUISize.cpp',
6709+ 'gui/SettingTypes/CGUIString.cpp',
6710+
6711+ 'i18n/L10n.cpp',
6712+ 'i18n/scripting/JSInterface_L10n.cpp',
6713+]
6714+gui_cpp_args = cpp_args
6715+gui_cpp_args += '-DLIB_STATIC_LINK=1 -DNVTT_SHARED=1'
6716+
6717+gui = static_library(
6718+ 'gui',
6719+ gui_sources,
6720+ include_directories: 'pch/gui/',
6721+ dependencies: [dep_boost, dep_icu, dep_tinygettext, dep_iconv, dep_js, dep_gl, dep_sdl2, dep_nvtt],
6722+ cpp_args: gui_cpp_args,
6723+)
6724+
6725+simulation_cpp_args = cpp_args
6726+simulation_cpp_args += '-DLIB_STATIC_LINK=1'
6727+simulation_sources = [
6728+ 'simulation2/components/CCmpAIManager.cpp',
6729+ 'simulation2/components/CCmpCinemaManager.cpp',
6730+ 'simulation2/components/CCmpCommandQueue.cpp',
6731+ 'simulation2/components/CCmpDecay.cpp',
6732+ 'simulation2/components/CCmpFootprint.cpp',
6733+ 'simulation2/components/CCmpMinimap.cpp',
6734+ 'simulation2/components/CCmpMotionBall.cpp',
6735+ 'simulation2/components/CCmpObstruction.cpp',
6736+ 'simulation2/components/CCmpObstructionManager.cpp',
6737+ 'simulation2/components/CCmpOverlayRenderer.cpp',
6738+ 'simulation2/components/CCmpOwnership.cpp',
6739+ 'simulation2/components/CCmpParticleManager.cpp',
6740+ 'simulation2/components/CCmpPathfinder.cpp',
6741+ 'simulation2/components/CCmpPosition.cpp',
6742+ 'simulation2/components/CCmpProjectileManager.cpp',
6743+ 'simulation2/components/CCmpRallyPointRenderer.cpp',
6744+ 'simulation2/components/CCmpRangeManager.cpp',
6745+ 'simulation2/components/CCmpRangeOverlayRenderer.cpp',
6746+ 'simulation2/components/CCmpSelectable.cpp',
6747+ 'simulation2/components/CCmpSoundManager.cpp',
6748+ 'simulation2/components/CCmpTemplateManager.cpp',
6749+ 'simulation2/components/CCmpTerrain.cpp',
6750+ 'simulation2/components/CCmpTerritoryInfluence.cpp',
6751+ 'simulation2/components/CCmpTerritoryManager.cpp',
6752+ 'simulation2/components/CCmpTest.cpp',
6753+ 'simulation2/components/CCmpUnitMotion.cpp',
6754+ 'simulation2/components/CCmpUnitRenderer.cpp',
6755+ 'simulation2/components/CCmpVision.cpp',
6756+ 'simulation2/components/CCmpVisualActor.cpp',
6757+ 'simulation2/components/CCmpWaterManager.cpp',
6758+ 'simulation2/components/ICmpAIInterface.cpp',
6759+ 'simulation2/components/ICmpAIManager.cpp',
6760+ 'simulation2/components/ICmpCinemaManager.cpp',
6761+ 'simulation2/components/ICmpCommandQueue.cpp',
6762+ 'simulation2/components/ICmpDecay.cpp',
6763+ 'simulation2/components/ICmpFogging.cpp',
6764+ 'simulation2/components/ICmpFootprint.cpp',
6765+ 'simulation2/components/ICmpGuiInterface.cpp',
6766+ 'simulation2/components/ICmpIdentity.cpp',
6767+ 'simulation2/components/ICmpMinimap.cpp',
6768+ 'simulation2/components/ICmpMirage.cpp',
6769+ 'simulation2/components/ICmpMotion.cpp',
6770+ 'simulation2/components/ICmpObstruction.cpp',
6771+ 'simulation2/components/ICmpObstructionManager.cpp',
6772+ 'simulation2/components/ICmpOverlayRenderer.cpp',
6773+ 'simulation2/components/ICmpOwnership.cpp',
6774+ 'simulation2/components/ICmpParticleManager.cpp',
6775+ 'simulation2/components/ICmpPathfinder.cpp',
6776+ 'simulation2/components/ICmpPlayer.cpp',
6777+ 'simulation2/components/ICmpPlayerManager.cpp',
6778+ 'simulation2/components/ICmpPosition.cpp',
6779+ 'simulation2/components/ICmpProjectileManager.cpp',
6780+ 'simulation2/components/ICmpRallyPoint.cpp',
6781+ 'simulation2/components/ICmpRallyPointRenderer.cpp',
6782+ 'simulation2/components/ICmpRangeManager.cpp',
6783+ 'simulation2/components/ICmpRangeOverlayRenderer.cpp',
6784+ 'simulation2/components/ICmpSelectable.cpp',
6785+ 'simulation2/components/ICmpSettlement.cpp',
6786+ 'simulation2/components/ICmpSound.cpp',
6787+ 'simulation2/components/ICmpSoundManager.cpp',
6788+ 'simulation2/components/ICmpTemplateManager.cpp',
6789+ 'simulation2/components/ICmpTerrain.cpp',
6790+ 'simulation2/components/ICmpTerritoryDecayManager.cpp',
6791+ 'simulation2/components/ICmpTerritoryInfluence.cpp',
6792+ 'simulation2/components/ICmpTerritoryManager.cpp',
6793+ 'simulation2/components/ICmpTest.cpp',
6794+ 'simulation2/components/ICmpUnitMotion.cpp',
6795+ 'simulation2/components/ICmpUnitRenderer.cpp',
6796+ 'simulation2/components/ICmpUnknownScript.cpp',
6797+ 'simulation2/components/ICmpValueModificationManager.cpp',
6798+ 'simulation2/components/ICmpVisibility.cpp',
6799+ 'simulation2/components/ICmpVision.cpp',
6800+ 'simulation2/components/ICmpVisual.cpp',
6801+ 'simulation2/components/ICmpWaterManager.cpp',
6802+ 'simulation2/helpers/CinemaPath.cpp',
6803+ 'simulation2/helpers/Geometry.cpp',
6804+ 'simulation2/helpers/HierarchicalPathfinder.cpp',
6805+ 'simulation2/helpers/LongPathfinder.cpp',
6806+ 'simulation2/helpers/PathGoal.cpp',
6807+ 'simulation2/helpers/Rasterize.cpp',
6808+ 'simulation2/helpers/Render.cpp',
6809+ 'simulation2/helpers/Selection.cpp',
6810+ 'simulation2/helpers/VertexPathfinder.cpp',
6811+ 'simulation2/scripting/EngineScriptConversions.cpp',
6812+ 'simulation2/scripting/JSInterface_Simulation.cpp',
6813+ 'simulation2/scripting/MessageTypeConversions.cpp',
6814+ 'simulation2/scripting/ScriptComponent.cpp',
6815+ 'simulation2/serialization/BinarySerializer.cpp',
6816+ 'simulation2/serialization/DebugSerializer.cpp',
6817+ 'simulation2/serialization/HashSerializer.cpp',
6818+ 'simulation2/serialization/IDeserializer.cpp',
6819+ 'simulation2/serialization/ISerializer.cpp',
6820+ 'simulation2/serialization/StdDeserializer.cpp',
6821+ 'simulation2/serialization/StdSerializer.cpp',
6822+ 'simulation2/Simulation2.cpp',
6823+ 'simulation2/system/CmpPtr.cpp',
6824+ 'simulation2/system/ComponentManager.cpp',
6825+ 'simulation2/system/ComponentManagerSerialization.cpp',
6826+ 'simulation2/system/DynamicSubscription.cpp',
6827+ 'simulation2/system/IComponent.cpp',
6828+ 'simulation2/system/LocalTurnManager.cpp',
6829+ 'simulation2/system/ParamNode.cpp',
6830+ 'simulation2/system/ReplayTurnManager.cpp',
6831+ 'simulation2/system/SimContext.cpp',
6832+ 'simulation2/system/TurnManager.cpp',
6833+]
6834+
6835+simulation = static_library(
6836+ 'simulation',
6837+ simulation_sources,
6838+ include_directories: 'pch/simulation2/',
6839+ dependencies: [dep_boost, dep_js, dep_gl],
6840+ cpp_args: simulation_cpp_args,
6841+ link_with: [lowlevel]
6842+)
6843+
6844+engine_cpp_args = cpp_args
6845+engine_cpp_args += '-DLIB_STATIC_LINK=1'
6846+engine_sources = [
6847+ 'third_party/cppformat/format.cpp',
6848+
6849+ 'i18n/L10n.cpp',
6850+ 'i18n/scripting/JSInterface_L10n.cpp',
6851+ 'maths/BoundingBoxAligned.cpp',
6852+ 'maths/BoundingBoxOriented.cpp',
6853+ 'maths/BoundingSphere.cpp',
6854+ 'maths/Brush.cpp',
6855+ 'maths/Fixed.cpp',
6856+ 'maths/Matrix3D.cpp',
6857+ 'maths/MD5.cpp',
6858+ 'maths/Noise.cpp',
6859+ 'maths/NUSpline.cpp',
6860+ 'maths/Plane.cpp',
6861+ 'maths/Quaternion.cpp',
6862+ 'maths/Sqrt.cpp',
6863+ 'maths/Vector3D.cpp',
6864+ 'ps/ArchiveBuilder.cpp',
6865+ 'ps/CacheLoader.cpp',
6866+ 'ps/CConsole.cpp',
6867+ 'ps/CLogger.cpp',
6868+ 'ps/Compress.cpp',
6869+ 'ps/ConfigDB.cpp',
6870+ 'ps/CStr.cpp',
6871+ 'ps/CStrIntern.cpp',
6872+ 'ps/DllLoader.cpp',
6873+ 'ps/Errors.cpp',
6874+ 'ps/FileIo.cpp',
6875+ 'ps/Filesystem.cpp',
6876+ 'ps/Game.cpp',
6877+ 'ps/GameSetup/Atlas.cpp',
6878+ 'ps/GameSetup/CmdLineArgs.cpp',
6879+ 'ps/GameSetup/Config.cpp',
6880+ 'ps/GameSetup/GameSetup.cpp',
6881+ 'ps/GameSetup/HWDetect.cpp',
6882+ 'ps/GameSetup/Paths.cpp',
6883+ 'ps/Globals.cpp',
6884+ 'ps/GUID.cpp',
6885+ 'ps/Hotkey.cpp',
6886+ 'ps/Joystick.cpp',
6887+ 'ps/KeyName.cpp',
6888+ 'ps/Loader.cpp',
6889+ 'ps/Mod.cpp',
6890+ 'ps/ModInstaller.cpp',
6891+ 'ps/ModIo.cpp',
6892+ 'ps/Profile.cpp',
6893+ 'ps/Profiler2.cpp',
6894+ 'ps/Profiler2GPU.cpp',
6895+ 'ps/ProfileViewer.cpp',
6896+ 'ps/Pyrogenesis.cpp',
6897+ 'ps/Replay.cpp',
6898+ 'ps/SavedGame.cpp',
6899+ 'ps/scripting/JSInterface_ConfigDB.cpp',
6900+ 'ps/scripting/JSInterface_Console.cpp',
6901+ 'ps/scripting/JSInterface_Debug.cpp',
6902+ 'ps/scripting/JSInterface_Game.cpp',
6903+ 'ps/scripting/JSInterface_Main.cpp',
6904+ 'ps/scripting/JSInterface_Mod.cpp',
6905+ 'ps/scripting/JSInterface_ModIo.cpp',
6906+ 'ps/scripting/JSInterface_SavedGame.cpp',
6907+ 'ps/scripting/JSInterface_UserReport.cpp',
6908+ 'ps/scripting/JSInterface_VFS.cpp',
6909+ 'ps/scripting/JSInterface_VisualReplay.cpp',
6910+ 'ps/Shapes.cpp',
6911+ 'ps/TemplateLoader.cpp',
6912+ 'ps/ThreadUtil.cpp',
6913+ 'ps/TouchInput.cpp',
6914+ 'ps/UserReport.cpp',
6915+ 'ps/Util.cpp',
6916+ 'ps/VideoMode.cpp',
6917+ 'ps/VisualReplay.cpp',
6918+ 'ps/World.cpp',
6919+ 'ps/XML/RelaxNG.cpp',
6920+ 'ps/XML/Xeromyces.cpp',
6921+ 'ps/XML/XeroXMB.cpp',
6922+ 'ps/XML/XMLWriter.cpp',
6923+]
6924+
6925+engine = static_library(
6926+ 'engine',
6927+ engine_sources,
6928+ include_directories: ['lib','pch/engine/'],
6929+ dependencies: [dep_boost, dep_js, dep_gl, dep_sdl2, dep_icu, dep_tinygettext, dep_iconv, dep_zlib, dep_curl, dep_sodium, dep_openal, dep_libxml2],
6930+ link_with: [gui, lowlevel, soundmanager],
6931+ cpp_args: engine_cpp_args,
6932+)
6933+
6934+
6935+graphics_sources = [
6936+ 'graphics/ICameraController.cpp',
6937+ 'graphics/CameraController.cpp',
6938+ 'graphics/Camera.cpp',
6939+ 'graphics/CinemaManager.cpp',
6940+ 'graphics/ColladaManager.cpp',
6941+ 'graphics/Color.cpp',
6942+ 'graphics/Decal.cpp',
6943+ 'graphics/Font.cpp',
6944+ 'graphics/FontManager.cpp',
6945+ 'graphics/FontMetrics.cpp',
6946+ 'graphics/Frustum.cpp',
6947+ 'graphics/GameView.cpp',
6948+ 'graphics/HeightMipmap.cpp',
6949+ 'graphics/HFTracer.cpp',
6950+ 'graphics/LightEnv.cpp',
6951+ 'graphics/LOSTexture.cpp',
6952+ 'graphics/MapGenerator.cpp',
6953+ 'graphics/MapIO.cpp',
6954+ 'graphics/MapReader.cpp',
6955+ 'graphics/MapWriter.cpp',
6956+ 'graphics/Material.cpp',
6957+ 'graphics/MaterialManager.cpp',
6958+ 'graphics/MeshManager.cpp',
6959+ 'graphics/MiniPatch.cpp',
6960+ 'graphics/ModelAbstract.cpp',
6961+ 'graphics/Model.cpp',
6962+ 'graphics/ModelDef.cpp',
6963+ 'graphics/ObjectBase.cpp',
6964+ 'graphics/ObjectEntry.cpp',
6965+ 'graphics/ObjectManager.cpp',
6966+ 'graphics/Overlay.cpp',
6967+ 'graphics/ParticleEmitter.cpp',
6968+ 'graphics/ParticleEmitterType.cpp',
6969+ 'graphics/ParticleManager.cpp',
6970+ 'graphics/Patch.cpp',
6971+ 'graphics/PreprocessorWrapper.cpp',
6972+ 'graphics/scripting/JSInterface_GameView.cpp',
6973+ 'graphics/ShaderDefines.cpp',
6974+ 'graphics/ShaderManager.cpp',
6975+ 'graphics/ShaderProgram.cpp',
6976+ 'graphics/ShaderProgramFFP.cpp',
6977+ 'graphics/ShaderTechnique.cpp',
6978+ 'graphics/SkeletonAnimDef.cpp',
6979+ 'graphics/SkeletonAnimManager.cpp',
6980+ 'graphics/SmoothedValue.cpp',
6981+ 'graphics/Terrain.cpp',
6982+ 'graphics/TerrainProperties.cpp',
6983+ 'graphics/TerrainTextureEntry.cpp',
6984+ 'graphics/TerrainTextureManager.cpp',
6985+ 'graphics/TerritoryBoundary.cpp',
6986+ 'graphics/TerritoryTexture.cpp',
6987+ 'graphics/TextRenderer.cpp',
6988+ 'graphics/TextureConverter.cpp',
6989+ 'graphics/TextureManager.cpp',
6990+ 'graphics/UnitAnimation.cpp',
6991+ 'graphics/Unit.cpp',
6992+ 'graphics/UnitManager.cpp',
6993+
6994+ 'renderer/AlphaMapCalculator.cpp',
6995+ 'renderer/DecalRData.cpp',
6996+ 'renderer/HWLightingModelRenderer.cpp',
6997+ 'renderer/InstancingModelRenderer.cpp',
6998+ 'renderer/MikktspaceWrap.cpp',
6999+ 'renderer/ModelRenderer.cpp',
7000+ 'renderer/OverlayRenderer.cpp',
7001+ 'renderer/ParticleRenderer.cpp',
7002+ 'renderer/PatchRData.cpp',
7003+ 'renderer/PostprocManager.cpp',
7004+ 'renderer/Renderer.cpp',
7005+ 'renderer/RenderingOptions.cpp',
7006+ 'renderer/RenderModifiers.cpp',
7007+ 'renderer/Scene.cpp',
7008+ 'renderer/scripting/JSInterface_Renderer.cpp',
7009+ 'renderer/ShadowMap.cpp',
7010+ 'renderer/SilhouetteRenderer.cpp',
7011+ 'renderer/SkyManager.cpp',
7012+ 'renderer/TerrainOverlay.cpp',
7013+ 'renderer/TerrainRenderer.cpp',
7014+ 'renderer/TexturedLineRData.cpp',
7015+ 'renderer/TimeManager.cpp',
7016+ 'renderer/VertexArray.cpp',
7017+ 'renderer/VertexBuffer.cpp',
7018+ 'renderer/VertexBufferManager.cpp',
7019+ 'renderer/WaterManager.cpp',
7020+
7021+ 'third_party/mikktspace/mikktspace.cpp',
7022+ 'third_party/mikktspace/weldmesh.cpp',
7023+ 'third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp',
7024+]
7025+graphics_cpp_args = cpp_args
7026+graphics_cpp_args += '-DLIB_STATIC_LINK=1 -DNVTT_SHARED=1'
7027+graphics = static_library(
7028+ 'graphics',
7029+ graphics_sources,
7030+ include_directories: 'pch/graphics/',
7031+ dependencies: [dep_boost, dep_gl, dep_js, dep_sdl2, dep_nvtt],
7032+ cpp_args: graphics_cpp_args,
7033+ link_with: [engine]
7034+)
7035+
7036+mongoose = static_library(
7037+ 'mongoose',
7038+ ['third_party/mongoose/mongoose.cpp'],
7039+ cpp_args: cpp_args,
7040+)
7041+
7042+scriptinterface_cpp_args = cpp_args
7043+scriptinterface_cpp_args += '-DLIB_STATIC_LINK=1'
7044+scriptinterface_sources = [
7045+ 'scriptinterface/ScriptConversions.cpp',
7046+ 'scriptinterface/ScriptInterface.cpp',
7047+ 'scriptinterface/ScriptRuntime.cpp',
7048+ 'scriptinterface/ScriptStats.cpp',
7049+]
7050+scriptinterface = static_library(
7051+ 'scriptinterface',
7052+ scriptinterface_sources,
7053+ include_directories: [
7054+ 'lib',
7055+ 'pch/scriptinterface/',
7056+ 'third_party/',
7057+ ],
7058+ dependencies: [dep_boost, dep_js,dep_valgrind],
7059+ cpp_args: scriptinterface_cpp_args,
7060+ link_with: [lowlevel]
7061+)
7062+
7063+mocks_real_cpp_args = cpp_args
7064+mocks_real_cpp_args += '-DLIB_STATIC_LINK=1'
7065+mocks_real = static_library(
7066+ 'mocks_real',
7067+ ['mocks/mocks_real.cpp'],
7068+ dependencies: [dep_boost],
7069+ cpp_args: mocks_real_cpp_args,
7070+)
7071+
7072+mocks_test = static_library(
7073+ 'mocks_test',
7074+ ['mocks/mocks_test.cpp'],
7075+ dependencies: [dep_boost],
7076+ cpp_args: cpp_args,
7077+)
7078+
7079+if get_option('atlas')
7080+ atlasobject_cpp_args = cpp_args
7081+ atlasobject_sources = [
7082+ 'tools/atlas/AtlasObject/AtlasObjectImpl.cpp',
7083+ 'tools/atlas/AtlasObject/AtlasObjectJS.cpp',
7084+ 'tools/atlas/AtlasObject/AtlasObjectText.cpp',
7085+ 'tools/atlas/AtlasObject/AtlasObjectXML.cpp',
7086+ ]
7087+ atlasobject = static_library(
7088+ 'AtlasObject',
7089+ atlasobject_sources,
7090+ include_directories: ['third_party/jsonspirit/', 'pch/atlas/'],
7091+ dependencies: [dep_boost],
7092+ link_with: [lowlevel],
7093+ cpp_args: simulation_cpp_args,
7094+ )
7095+ atlasui_cpp_args = cpp_args
7096+ atlasui_sources = [
7097+ 'tools/atlas/AtlasUI/ActorEditor/ActorEditor.cpp',
7098+ 'tools/atlas/AtlasUI/ActorEditor/ActorEditorListCtrl.cpp',
7099+ 'tools/atlas/AtlasUI/ActorEditor/AnimListEditor.cpp',
7100+ 'tools/atlas/AtlasUI/ActorEditor/PropListEditor.cpp',
7101+ 'tools/atlas/AtlasUI/ActorEditor/TexListEditor.cpp',
7102+ 'tools/atlas/AtlasUI/CustomControls/Buttons/ActionButton.cpp',
7103+ 'tools/atlas/AtlasUI/CustomControls/Buttons/ToolButton.cpp',
7104+ 'tools/atlas/AtlasUI/CustomControls/Canvas/Canvas.cpp',
7105+ 'tools/atlas/AtlasUI/CustomControls/ColorDialog/ColorDialog.cpp',
7106+ 'tools/atlas/AtlasUI/CustomControls/DraggableListCtrl/DraggableListCtrlCommands.cpp',
7107+ 'tools/atlas/AtlasUI/CustomControls/DraggableListCtrl/DraggableListCtrl.cpp',
7108+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/EditableListCtrlCommands.cpp',
7109+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/EditableListCtrl.cpp',
7110+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/FieldEditCtrl.cpp',
7111+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/ListCtrlValidator.cpp',
7112+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/QuickComboBox.cpp',
7113+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/QuickFileCtrl.cpp',
7114+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/QuickTextCtrl.cpp',
7115+ 'tools/atlas/AtlasUI/CustomControls/FileHistory/FileHistory.cpp',
7116+ 'tools/atlas/AtlasUI/CustomControls/HighResTimer/HighResTimer.cpp',
7117+ 'tools/atlas/AtlasUI/CustomControls/MapDialog/MapDialog.cpp',
7118+ 'tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.cpp',
7119+ 'tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/virtualdirtreectrl.cpp',
7120+ 'tools/atlas/AtlasUI/CustomControls/Windows/AtlasDialog.cpp',
7121+ 'tools/atlas/AtlasUI/CustomControls/Windows/AtlasWindow.cpp',
7122+ 'tools/atlas/AtlasUI/General/AtlasClipboard.cpp',
7123+ 'tools/atlas/AtlasUI/General/AtlasEventLoop.cpp',
7124+ 'tools/atlas/AtlasUI/General/AtlasWindowCommand.cpp',
7125+ 'tools/atlas/AtlasUI/General/AtlasWindowCommandProc.cpp',
7126+ 'tools/atlas/AtlasUI/General/Datafile.cpp',
7127+ 'tools/atlas/AtlasUI/General/Observable.cpp',
7128+ 'tools/atlas/AtlasUI/Misc/DLLInterface.cpp',
7129+ 'tools/atlas/AtlasUI/Misc/KeyMap.cpp',
7130+ 'tools/atlas/AtlasUI/Misc/precompiled.cpp',
7131+ 'tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp',
7132+ 'tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp',
7133+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Cinema/Cinema.cpp',
7134+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.cpp',
7135+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp',
7136+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/LightControl.cpp',
7137+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp',
7138+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp',
7139+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/VariationControl.cpp',
7140+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp',
7141+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp',
7142+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/ActorViewerTool.cpp',
7143+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/AlterElevation.cpp',
7144+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp',
7145+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/MiscState.cpp',
7146+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp',
7147+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.cpp',
7148+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/FillTerrain.cpp',
7149+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/FlattenElevation.cpp',
7150+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/PaintTerrain.cpp',
7151+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/PickWaterHeight.cpp',
7152+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/PikeElevation.cpp',
7153+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/PlaceObject.cpp',
7154+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/ReplaceTerrain.cpp',
7155+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/SmoothElevation.cpp',
7156+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/TransformObject.cpp',
7157+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/TransformPath.cpp',
7158+ ]
7159+
7160+ atlasui = library(
7161+ 'AtlasUI', # Called in Atlas.cpp
7162+ atlasui_sources,
7163+ include_directories: [
7164+ 'pch/atlas/',
7165+ 'tools/atlas/AtlasUI/CustomControls/',
7166+ ],
7167+ dependencies: [dep_iconv, dep_libxml2, dep_boost, dep_js],
7168+ link_with: [atlasobject],
7169+ cpp_args: atlasui_cpp_args,
7170+ )
7171+
7172+ actoreditor_sources = [
7173+ 'tools/atlas/AtlasFrontends/ActorEditor.cpp',
7174+ ]
7175+ executable(
7176+ 'actoreditor',
7177+ actoreditor_sources,
7178+ dependencies: dependencies,
7179+ link_with: [atlasobject, atlasui],
7180+ cpp_args: cpp_args,
7181+ install: true
7182+ )
7183+endif
7184+
7185+
7186+
7187+atlas_sources = [
7188+
7189+# 'tools/atlas/AtlasFrontends/_template.cpp',
7190+ 'tools/atlas/GameInterface/ActorViewer.cpp',
7191+ 'tools/atlas/GameInterface/Brushes.cpp',
7192+ 'tools/atlas/GameInterface/CommandProc.cpp',
7193+ 'tools/atlas/GameInterface/GameLoop.cpp',
7194+ 'tools/atlas/GameInterface/InputProcessor.cpp',
7195+ 'tools/atlas/GameInterface/MessagePasserImpl.cpp',
7196+ 'tools/atlas/GameInterface/Misc.cpp',
7197+ 'tools/atlas/GameInterface/Register.cpp',
7198+ 'tools/atlas/GameInterface/SimState.cpp',
7199+ 'tools/atlas/GameInterface/View.cpp',
7200+ 'tools/atlas/GameInterface/Handlers/BrushHandlers.cpp',
7201+ 'tools/atlas/GameInterface/Handlers/CameraCtrlHandlers.cpp',
7202+ 'tools/atlas/GameInterface/Handlers/CinemaHandler.cpp',
7203+ 'tools/atlas/GameInterface/Handlers/CommandHandlers.cpp',
7204+ 'tools/atlas/GameInterface/Handlers/ElevationHandlers.cpp',
7205+ 'tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp',
7206+ 'tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp',
7207+ 'tools/atlas/GameInterface/Handlers/MapHandlers.cpp',
7208+ 'tools/atlas/GameInterface/Handlers/MessageHandler.cpp',
7209+ 'tools/atlas/GameInterface/Handlers/MiscHandlers.cpp',
7210+ 'tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp',
7211+ 'tools/atlas/GameInterface/Handlers/PlayerHandlers.cpp',
7212+ 'tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp',
7213+]
7214+atlas_cpp_args = cpp_args
7215+atlas_cpp_args += '-DLIB_STATIC_LINK=1'
7216+atlas = static_library(
7217+ 'atlas',
7218+ atlas_sources,
7219+ include_directories: [
7220+ 'pch/atlas/',
7221+ ],
7222+ dependencies: [dep_boost, dep_js, dep_gl, dep_sdl2],
7223+ cpp_args: atlas_cpp_args,
7224+)
7225+
7226+
7227+pyrogenesis_cpp_args = cpp_args
7228+sources = [
7229+ 'main.cpp',
7230+
7231+]
7232+if host_machine.system() == 'windows'
7233+windows = import('windows')
7234+ pyrogenesis_cpp_args += '-DLIB_STATIC_LINK=1 -DHAVE_ICONV_CONST=1 -Dtinygettext_ICONV_CONST=const -DICONV_CONST=const -DNVTT_SHARED=1'
7235+ sources += [
7236+ windows.compile_resources('lib/sysdep/os/win/error_dialog.rc'),
7237+ windows.compile_resources('lib/sysdep/os/win/icon.rc'),
7238+ ]
7239+endif
7240+
7241+executable(
7242+ 'pyrogenesis',
7243+ sources,
7244+ include_directories: 'lib',
7245+ link_with: [atlas, engine, glooxwrapper, graphics, gui, lobby, lowlevel, mocks_real, mongoose, network, scriptinterface, simulation, tinygettext],
7246+ dependencies: [dep_boost, dep_gl, dep_sdl2, dep_js],
7247+ cpp_args: pyrogenesis_cpp_args,
7248+ install: true
7249+)
7250+
7251+
7252+# pyrogenesis_test_sources = [
7253+# 'graphics/tests/test_Camera.cpp',
7254+# # 'gui/tests/test_*',
7255+# # 'lib/tests/test_*',
7256+# # 'maths/tests/test_*',
7257+# # 'network/tests/test_*',
7258+# # 'ps/tests/test_*',
7259+# # 'scriptinterface/tests/test_*',
7260+# # 'simulation2/tests/test_*',
7261+# # 'simulation2/components/tests/test_*',
7262+# # 'third_party/encryption/tests/test_*',
7263+# # 'third_party/ogre3d_preprocessor/tests/test_*',
7264+# # 'tools/atlas/tests/test_*',
7265+# # 'ps/GameSetup/tests/test_*',
7266+# # 'ps/XML/tests/test_*',
7267+# # 'test_setup.cpp',
7268+
7269+# ]
7270+
7271+
7272+# executable(
7273+# 'pyrogenesis-test',
7274+# pyrogenesis_test_sources,
7275+# include_directories: [
7276+# 'lib',
7277+# 'pch/test/',
7278+# ],
7279+# dependencies: dependencies,
7280+# link_with: [atlas, engine, glooxwrapper, graphics, gui, lobby, lowlevel, mocks_test, mongoose, network, scriptinterface, simulation, tinygettext],
7281+# cpp_args: pyrogenesis_cpp_args,
7282+# install: true
7283+# )
7284Index: source/ps/CConsole.cpp
7285===================================================================
7286--- source/ps/CConsole.cpp (revision 23275)
7287+++ source/ps/CConsole.cpp (working copy)
7288@@ -1,699 +1,700 @@
7289 /* Copyright (C) 2019 Wildfire Games.
7290 * This file is part of 0 A.D.
7291 *
7292 * 0 A.D. is free software: you can redistribute it and/or modify
7293 * it under the terms of the GNU General Public License as published by
7294 * the Free Software Foundation, either version 2 of the License, or
7295 * (at your option) any later version.
7296 *
7297 * 0 A.D. is distributed in the hope that it will be useful,
7298 * but WITHOUT ANY WARRANTY; without even the implied warranty of
7299 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7300 * GNU General Public License for more details.
7301 *
7302 * You should have received a copy of the GNU General Public License
7303 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
7304 */
7305
7306 /*
7307 * Implements the in-game console with scripting support.
7308 */
7309
7310 #include "precompiled.h"
7311 #include <wctype.h>
7312
7313 #include "CConsole.h"
7314
7315 #include "graphics/FontMetrics.h"
7316 #include "graphics/ShaderManager.h"
7317 #include "graphics/TextRenderer.h"
7318 #include "gui/CGUI.h"
7319 #include "gui/GUIManager.h"
7320 #include "gui/GUIMatrix.h"
7321 #include "lib/ogl.h"
7322-#include "lib/sysdep/clipboard.h"
7323 #include "lib/timer.h"
7324 #include "lib/utf8.h"
7325 #include "maths/MathUtil.h"
7326 #include "network/NetClient.h"
7327 #include "network/NetServer.h"
7328 #include "ps/CLogger.h"
7329 #include "ps/Filesystem.h"
7330 #include "ps/GameSetup/Config.h"
7331 #include "ps/Globals.h"
7332 #include "ps/Hotkey.h"
7333 #include "ps/Profile.h"
7334 #include "ps/Pyrogenesis.h"
7335 #include "renderer/Renderer.h"
7336 #include "scriptinterface/ScriptInterface.h"
7337
7338 CConsole* g_Console = 0;
7339
7340 CConsole::CConsole()
7341 {
7342 m_bToggle = false;
7343 m_bVisible = false;
7344
7345 m_fVisibleFrac = 0.0f;
7346
7347 m_szBuffer = new wchar_t[CONSOLE_BUFFER_SIZE];
7348 FlushBuffer();
7349
7350 m_iMsgHistPos = 1;
7351 m_charsPerPage = 0;
7352
7353 m_prevTime = 0.0;
7354 m_bCursorVisState = true;
7355 m_cursorBlinkRate = 0.5;
7356
7357 InsertMessage("[ 0 A.D. Console v0.14 ]");
7358 InsertMessage("");
7359 }
7360
7361 CConsole::~CConsole()
7362 {
7363 delete[] m_szBuffer;
7364 }
7365
7366
7367 void CConsole::SetSize(float X, float Y, float W, float H)
7368 {
7369 m_fX = X;
7370 m_fY = Y;
7371 m_fWidth = W;
7372 m_fHeight = H;
7373 }
7374
7375 void CConsole::UpdateScreenSize(int w, int h)
7376 {
7377 float height = h * 0.6f;
7378 SetSize(0, 0, w / g_GuiScale, height / g_GuiScale);
7379 }
7380
7381
7382 void CConsole::ToggleVisible()
7383 {
7384 m_bToggle = true;
7385 m_bVisible = !m_bVisible;
7386
7387 // TODO: this should be based on input focus, not visibility
7388 if (m_bVisible)
7389 SDL_StartTextInput();
7390 else
7391 SDL_StopTextInput();
7392 }
7393
7394 void CConsole::SetVisible(bool visible)
7395 {
7396 if (visible != m_bVisible)
7397 m_bToggle = true;
7398 m_bVisible = visible;
7399 if (visible)
7400 {
7401 m_prevTime = 0.0;
7402 m_bCursorVisState = false;
7403 }
7404 }
7405
7406 void CConsole::SetCursorBlinkRate(double rate)
7407 {
7408 m_cursorBlinkRate = rate;
7409 }
7410
7411 void CConsole::FlushBuffer()
7412 {
7413 // Clear the buffer and set the cursor and length to 0
7414 memset(m_szBuffer, '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE);
7415 m_iBufferPos = m_iBufferLength = 0;
7416 }
7417
7418
7419 void CConsole::Update(const float deltaRealTime)
7420 {
7421 if(m_bToggle)
7422 {
7423 const float AnimateTime = .30f;
7424 const float Delta = deltaRealTime / AnimateTime;
7425 if(m_bVisible)
7426 {
7427 m_fVisibleFrac += Delta;
7428 if(m_fVisibleFrac > 1.0f)
7429 {
7430 m_fVisibleFrac = 1.0f;
7431 m_bToggle = false;
7432 }
7433 }
7434 else
7435 {
7436 m_fVisibleFrac -= Delta;
7437 if(m_fVisibleFrac < 0.0f)
7438 {
7439 m_fVisibleFrac = 0.0f;
7440 m_bToggle = false;
7441 }
7442 }
7443 }
7444 }
7445
7446 //Render Manager.
7447 void CConsole::Render()
7448 {
7449 if (! (m_bVisible || m_bToggle) ) return;
7450
7451 PROFILE3_GPU("console");
7452
7453 glEnable(GL_BLEND);
7454 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
7455
7456 CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
7457 solidTech->BeginPass();
7458 CShaderProgramPtr solidShader = solidTech->GetShader();
7459
7460 CMatrix3D transform = GetDefaultGuiMatrix();
7461
7462 // animation: slide in from top of screen
7463 const float DeltaY = (1.0f - m_fVisibleFrac) * m_fHeight;
7464 transform.PostTranslate(m_fX, m_fY - DeltaY, 0.0f); // move to window position
7465 solidShader->Uniform(str_transform, transform);
7466
7467 DrawWindow(solidShader);
7468
7469 solidTech->EndPass();
7470
7471 CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
7472 textTech->BeginPass();
7473 CTextRenderer textRenderer(textTech->GetShader());
7474 textRenderer.Font(CStrIntern(CONSOLE_FONT));
7475 textRenderer.SetTransform(transform);
7476
7477 DrawHistory(textRenderer);
7478 DrawBuffer(textRenderer);
7479
7480 textRenderer.Render();
7481
7482 textTech->EndPass();
7483
7484 glDisable(GL_BLEND);
7485 }
7486
7487
7488 void CConsole::DrawWindow(CShaderProgramPtr& shader)
7489 {
7490 float boxVerts[] = {
7491 m_fWidth, 0.0f,
7492 1.0f, 0.0f,
7493 1.0f, m_fHeight-1.0f,
7494 m_fWidth, m_fHeight-1.0f
7495 };
7496
7497 shader->VertexPointer(2, GL_FLOAT, 0, boxVerts);
7498
7499 // Draw Background
7500 // Set the color to a translucent blue
7501 shader->Uniform(str_color, 0.0f, 0.0f, 0.5f, 0.6f);
7502 shader->AssertPointersBound();
7503 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
7504
7505 // Draw Border
7506 // Set the color to a translucent yellow
7507 shader->Uniform(str_color, 0.5f, 0.5f, 0.0f, 0.6f);
7508 shader->AssertPointersBound();
7509 glDrawArrays(GL_LINE_LOOP, 0, 4);
7510
7511 if (m_fHeight > m_iFontHeight + 4)
7512 {
7513 float lineVerts[] = {
7514 0.0f, m_fHeight - (float)m_iFontHeight - 4.0f,
7515 m_fWidth, m_fHeight - (float)m_iFontHeight - 4.0f
7516 };
7517 shader->VertexPointer(2, GL_FLOAT, 0, lineVerts);
7518 shader->AssertPointersBound();
7519 glDrawArrays(GL_LINES, 0, 2);
7520 }
7521 }
7522
7523
7524 void CConsole::DrawHistory(CTextRenderer& textRenderer)
7525 {
7526 int i = 1;
7527
7528 std::deque<std::wstring>::iterator Iter; //History iterator
7529
7530 std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
7531
7532 textRenderer.Color(1.0f, 1.0f, 1.0f);
7533
7534 for (Iter = m_deqMsgHistory.begin();
7535 Iter != m_deqMsgHistory.end()
7536 && (((i - m_iMsgHistPos + 1) * m_iFontHeight) < m_fHeight);
7537 ++Iter)
7538 {
7539 if (i >= m_iMsgHistPos)
7540 textRenderer.Put(9.0f, m_fHeight - (float)m_iFontOffset - (float)m_iFontHeight * (i - m_iMsgHistPos + 1), Iter->c_str());
7541
7542 i++;
7543 }
7544 }
7545
7546 // Renders the buffer to the screen.
7547 void CConsole::DrawBuffer(CTextRenderer& textRenderer)
7548 {
7549 if (m_fHeight < m_iFontHeight)
7550 return;
7551
7552 CMatrix3D savedTransform = textRenderer.GetTransform();
7553
7554 textRenderer.Translate(2.0f, m_fHeight - (float)m_iFontOffset + 1.0f, 0.0f);
7555
7556 textRenderer.Color(1.0f, 1.0f, 0.0f);
7557 textRenderer.PutAdvance(L"]");
7558
7559 textRenderer.Color(1.0f, 1.0f, 1.0f);
7560
7561 if (m_iBufferPos == 0)
7562 DrawCursor(textRenderer);
7563
7564 for (int i = 0; i < m_iBufferLength; i++)
7565 {
7566 textRenderer.PrintfAdvance(L"%lc", m_szBuffer[i]);
7567 if (m_iBufferPos-1 == i)
7568 DrawCursor(textRenderer);
7569 }
7570
7571 textRenderer.SetTransform(savedTransform);
7572 }
7573
7574 void CConsole::DrawCursor(CTextRenderer& textRenderer)
7575 {
7576 if (m_cursorBlinkRate > 0.0)
7577 {
7578 // check if the cursor visibility state needs to be changed
7579 double currTime = timer_Time();
7580 if ((currTime - m_prevTime) >= m_cursorBlinkRate)
7581 {
7582 m_bCursorVisState = !m_bCursorVisState;
7583 m_prevTime = currTime;
7584 }
7585 }
7586 else
7587 {
7588 // Should always be visible
7589 m_bCursorVisState = true;
7590 }
7591
7592 if(m_bCursorVisState)
7593 {
7594 // Slightly translucent yellow
7595 textRenderer.Color(1.0f, 1.0f, 0.0f, 0.8f);
7596
7597 // Cursor character is chosen to be an underscore
7598 textRenderer.Put(0.0f, 0.0f, L"_");
7599
7600 // Revert to the standard text color
7601 textRenderer.Color(1.0f, 1.0f, 1.0f);
7602 }
7603 }
7604
7605
7606 //Inserts a character into the buffer.
7607 void CConsole::InsertChar(const int szChar, const wchar_t cooked)
7608 {
7609 static int iHistoryPos = -1;
7610
7611 if (!m_bVisible) return;
7612
7613 switch (szChar)
7614 {
7615 case SDLK_RETURN:
7616 iHistoryPos = -1;
7617 m_iMsgHistPos = 1;
7618 ProcessBuffer(m_szBuffer);
7619 FlushBuffer();
7620 return;
7621
7622 case SDLK_TAB:
7623 // Auto Complete
7624 return;
7625
7626 case SDLK_BACKSPACE:
7627 if (IsEmpty() || IsBOB()) return;
7628
7629 if (m_iBufferPos == m_iBufferLength)
7630 m_szBuffer[m_iBufferPos - 1] = '\0';
7631 else
7632 {
7633 for (int j = m_iBufferPos-1; j < m_iBufferLength-1; j++)
7634 m_szBuffer[j] = m_szBuffer[j+1]; // move chars to left
7635 m_szBuffer[m_iBufferLength-1] = '\0';
7636 }
7637
7638 m_iBufferPos--;
7639 m_iBufferLength--;
7640 return;
7641
7642 case SDLK_DELETE:
7643 if (IsEmpty() || IsEOB()) return;
7644
7645 if (m_iBufferPos == m_iBufferLength-1)
7646 {
7647 m_szBuffer[m_iBufferPos] = '\0';
7648 m_iBufferLength--;
7649 }
7650 else
7651 {
7652 if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
7653 {
7654 // Make Ctrl-Delete delete up to end of line
7655 m_szBuffer[m_iBufferPos] = '\0';
7656 m_iBufferLength = m_iBufferPos;
7657 }
7658 else
7659 {
7660 // Delete just one char and move the others left
7661 for(int j=m_iBufferPos; j<m_iBufferLength-1; j++)
7662 m_szBuffer[j] = m_szBuffer[j+1];
7663 m_szBuffer[m_iBufferLength-1] = '\0';
7664 m_iBufferLength--;
7665 }
7666 }
7667
7668 return;
7669
7670 case SDLK_HOME:
7671 if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
7672 {
7673 std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
7674
7675 int linesShown = (int)m_fHeight/m_iFontHeight - 4;
7676 m_iMsgHistPos = Clamp(static_cast<int>(m_deqMsgHistory.size()) - linesShown, 1, static_cast<int>(m_deqMsgHistory.size()));
7677 }
7678 else
7679 {
7680 m_iBufferPos = 0;
7681 }
7682 return;
7683
7684 case SDLK_END:
7685 if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
7686 {
7687 m_iMsgHistPos = 1;
7688 }
7689 else
7690 {
7691 m_iBufferPos = m_iBufferLength;
7692 }
7693 return;
7694
7695 case SDLK_LEFT:
7696 if (m_iBufferPos) m_iBufferPos--;
7697 return;
7698
7699 case SDLK_RIGHT:
7700 if (m_iBufferPos != m_iBufferLength) m_iBufferPos++;
7701 return;
7702
7703 // BEGIN: Buffer History Lookup
7704 case SDLK_UP:
7705 if (m_deqBufHistory.size() && iHistoryPos != (int)m_deqBufHistory.size() - 1)
7706 {
7707 iHistoryPos++;
7708 SetBuffer(m_deqBufHistory.at(iHistoryPos).c_str());
7709 m_iBufferPos = m_iBufferLength;
7710 }
7711 return;
7712
7713 case SDLK_DOWN:
7714 if (m_deqBufHistory.size())
7715 {
7716 if (iHistoryPos > 0)
7717 {
7718 iHistoryPos--;
7719 SetBuffer(m_deqBufHistory.at(iHistoryPos).c_str());
7720 m_iBufferPos = m_iBufferLength;
7721 }
7722 else if (iHistoryPos == 0)
7723 {
7724 iHistoryPos--;
7725 FlushBuffer();
7726 }
7727 }
7728 return;
7729 // END: Buffer History Lookup
7730
7731 // BEGIN: Message History Lookup
7732 case SDLK_PAGEUP:
7733 {
7734 std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
7735
7736 if (m_iMsgHistPos != (int)m_deqMsgHistory.size()) m_iMsgHistPos++;
7737 return;
7738 }
7739
7740 case SDLK_PAGEDOWN:
7741 if (m_iMsgHistPos != 1) m_iMsgHistPos--;
7742 return;
7743 // END: Message History Lookup
7744
7745 default: //Insert a character
7746 if (IsFull()) return;
7747 if (cooked == 0) return;
7748
7749 if (IsEOB()) //are we at the end of the buffer?
7750 m_szBuffer[m_iBufferPos] = cooked; //cat char onto end
7751 else
7752 { //we need to insert
7753 int i;
7754 for(i=m_iBufferLength; i>m_iBufferPos; i--)
7755 m_szBuffer[i] = m_szBuffer[i-1]; // move chars to right
7756 m_szBuffer[i] = cooked;
7757 }
7758
7759 m_iBufferPos++;
7760 m_iBufferLength++;
7761
7762 return;
7763 }
7764 }
7765
7766
7767 void CConsole::InsertMessage(const std::string& message)
7768 {
7769 // (TODO: this text-wrapping is rubbish since we now use variable-width fonts)
7770
7771 //Insert newlines to wraparound text where needed
7772 std::wstring wrapAround = wstring_from_utf8(message.c_str());
7773 std::wstring newline(L"\n");
7774 size_t oldNewline=0;
7775 size_t distance;
7776
7777 //make sure everything has been initialized
7778 if ( m_charsPerPage != 0 )
7779 {
7780 while ( oldNewline+m_charsPerPage < wrapAround.length() )
7781 {
7782 distance = wrapAround.find(newline, oldNewline) - oldNewline;
7783 if ( distance > m_charsPerPage )
7784 {
7785 oldNewline += m_charsPerPage;
7786 wrapAround.insert( oldNewline++, newline );
7787 }
7788 else
7789 oldNewline += distance+1;
7790 }
7791 }
7792 // Split into lines and add each one individually
7793 oldNewline = 0;
7794
7795 {
7796 std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
7797
7798 while ( (distance = wrapAround.find(newline, oldNewline)) != wrapAround.npos)
7799 {
7800 distance -= oldNewline;
7801 m_deqMsgHistory.push_front(wrapAround.substr(oldNewline, distance));
7802 oldNewline += distance+1;
7803 }
7804 m_deqMsgHistory.push_front(wrapAround.substr(oldNewline));
7805 }
7806 }
7807
7808 const wchar_t* CConsole::GetBuffer()
7809 {
7810 m_szBuffer[m_iBufferLength] = 0;
7811 return( m_szBuffer );
7812 }
7813
7814 void CConsole::SetBuffer(const wchar_t* szMessage)
7815 {
7816 int oldBufferPos = m_iBufferPos; // remember since FlushBuffer will set it to 0
7817
7818 FlushBuffer();
7819
7820 wcsncpy(m_szBuffer, szMessage, CONSOLE_BUFFER_SIZE);
7821 m_szBuffer[CONSOLE_BUFFER_SIZE-1] = 0;
7822- m_iBufferLength = (int)wcslen(m_szBuffer);
7823+ m_iBufferLength = static_cast<int>(wcslen(m_szBuffer));
7824 m_iBufferPos = std::min(oldBufferPos, m_iBufferLength);
7825 }
7826
7827 void CConsole::UseHistoryFile(const VfsPath& filename, int max_history_lines)
7828 {
7829 m_MaxHistoryLines = max_history_lines;
7830
7831 m_sHistoryFile = filename;
7832 LoadHistory();
7833 }
7834
7835 void CConsole::ProcessBuffer(const wchar_t* szLine)
7836 {
7837 shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
7838 JSContext* cx = pScriptInterface->GetContext();
7839 JSAutoRequest rq(cx);
7840
7841 if (szLine == NULL) return;
7842 if (wcslen(szLine) <= 0) return;
7843
7844 ENSURE(wcslen(szLine) < CONSOLE_BUFFER_SIZE);
7845
7846 m_deqBufHistory.push_front(szLine);
7847 SaveHistory(); // Do this each line for the moment; if a script causes
7848 // a crash it's a useful record.
7849
7850 // Process it as JavaScript
7851
7852 JS::RootedValue rval(cx);
7853 pScriptInterface->Eval(szLine, &rval);
7854 if (!rval.isUndefined())
7855 InsertMessage(pScriptInterface->ToString(&rval));
7856 }
7857
7858 void CConsole::LoadHistory()
7859 {
7860 // note: we don't care if this file doesn't exist or can't be read;
7861 // just don't load anything in that case.
7862
7863 // do this before LoadFile to avoid an error message if file not found.
7864 if (!VfsFileExists(m_sHistoryFile))
7865 return;
7866
7867 shared_ptr<u8> buf; size_t buflen;
7868 if (g_VFS->LoadFile(m_sHistoryFile, buf, buflen) < 0)
7869 return;
7870
7871 CStr bytes ((char*)buf.get(), buflen);
7872
7873 CStrW str (bytes.FromUTF8());
7874 size_t pos = 0;
7875 while (pos != CStrW::npos)
7876 {
7877 pos = str.find('\n');
7878 if (pos != CStrW::npos)
7879 {
7880 if (pos > 0)
7881 m_deqBufHistory.push_front(str.Left(str[pos-1] == '\r' ? pos - 1 : pos));
7882 str = str.substr(pos + 1);
7883 }
7884 else if (str.length() > 0)
7885 m_deqBufHistory.push_front(str);
7886 }
7887 }
7888
7889 void CConsole::SaveHistory()
7890 {
7891 WriteBuffer buffer;
7892 const int linesToSkip = (int)m_deqBufHistory.size() - m_MaxHistoryLines;
7893 std::deque<std::wstring>::reverse_iterator it = m_deqBufHistory.rbegin();
7894 if(linesToSkip > 0)
7895 std::advance(it, linesToSkip);
7896 for (; it != m_deqBufHistory.rend(); ++it)
7897 {
7898 CStr8 line = CStrW(*it).ToUTF8();
7899 buffer.Append(line.data(), line.length());
7900 static const char newline = '\n';
7901 buffer.Append(&newline, 1);
7902 }
7903 g_VFS->CreateFile(m_sHistoryFile, buffer.Data(), buffer.Size());
7904 }
7905
7906 static bool isUnprintableChar(SDL_Keysym key)
7907 {
7908 switch (key.sym)
7909 {
7910 // We want to allow some, which are handled specially
7911 case SDLK_RETURN: case SDLK_TAB:
7912 case SDLK_BACKSPACE: case SDLK_DELETE:
7913 case SDLK_HOME: case SDLK_END:
7914 case SDLK_LEFT: case SDLK_RIGHT:
7915 case SDLK_UP: case SDLK_DOWN:
7916 case SDLK_PAGEUP: case SDLK_PAGEDOWN:
7917 return false;
7918
7919 // Ignore the others
7920 default:
7921 return true;
7922 }
7923 }
7924
7925 InReaction conInputHandler(const SDL_Event_* ev)
7926 {
7927 if (!g_Console)
7928 return IN_PASS;
7929
7930 if ((int)ev->ev.type == SDL_HOTKEYDOWN)
7931 {
7932 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
7933
7934 if (hotkey == "console.toggle")
7935 {
7936 g_Console->ToggleVisible();
7937 return IN_HANDLED;
7938 }
7939 else if (g_Console->IsActive() && hotkey == "copy")
7940 {
7941- sys_clipboard_set(g_Console->GetBuffer());
7942+ std::string text = utf8_from_wstring(g_Console->GetBuffer());
7943+ SDL_SetClipboardText(text.c_str());
7944 return IN_HANDLED;
7945 }
7946 else if (g_Console->IsActive() && hotkey == "paste")
7947 {
7948- wchar_t* text = sys_clipboard_get();
7949+ char* text = SDL_GetClipboardText();
7950 if (text)
7951 {
7952- for (wchar_t* c = text; *c; c++)
7953- g_Console->InsertChar(0, *c);
7954+ std::wstring wtext = wstring_from_utf8(text);
7955+ for (wchar_t c : wtext)
7956+ g_Console->InsertChar(0, c);
7957
7958- sys_clipboard_free(text);
7959+ SDL_free(text);
7960 }
7961 return IN_HANDLED;
7962 }
7963 }
7964
7965 if (!g_Console->IsActive())
7966 return IN_PASS;
7967
7968 // In SDL2, we no longer get Unicode wchars via SDL_Keysym
7969 // we use text input events instead and they provide UTF-8 chars
7970 if (ev->ev.type == SDL_TEXTINPUT && !HotkeyIsPressed("console.toggle"))
7971 {
7972 // TODO: this could be more efficient with an interface to insert UTF-8 strings directly
7973 std::wstring wstr = wstring_from_utf8(ev->ev.text.text);
7974 for (size_t i = 0; i < wstr.length(); ++i)
7975 g_Console->InsertChar(0, wstr[i]);
7976 return IN_HANDLED;
7977 }
7978 // TODO: text editing events for IME support
7979
7980 if (ev->ev.type != SDL_KEYDOWN)
7981 return IN_PASS;
7982
7983 int sym = ev->ev.key.keysym.sym;
7984
7985 // Stop unprintable characters (ctrl+, alt+ and escape),
7986 // also prevent ` and/or ~ appearing in console every time it's toggled.
7987 if (!isUnprintableChar(ev->ev.key.keysym) &&
7988 !HotkeyIsPressed("console.toggle"))
7989 {
7990 g_Console->InsertChar(sym, 0);
7991 return IN_HANDLED;
7992 }
7993
7994 return IN_PASS;
7995 }
7996Index: source/ps/CLogger.cpp
7997===================================================================
7998--- source/ps/CLogger.cpp (revision 23275)
7999+++ source/ps/CLogger.cpp (working copy)
8000@@ -1,341 +1,342 @@
8001 /* Copyright (C) 2019 Wildfire Games.
8002 * This file is part of 0 A.D.
8003 *
8004 * 0 A.D. is free software: you can redistribute it and/or modify
8005 * it under the terms of the GNU General Public License as published by
8006 * the Free Software Foundation, either version 2 of the License, or
8007 * (at your option) any later version.
8008 *
8009 * 0 A.D. is distributed in the hope that it will be useful,
8010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8012 * GNU General Public License for more details.
8013 *
8014 * You should have received a copy of the GNU General Public License
8015 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
8016 */
8017
8018 #include "precompiled.h"
8019
8020 #include "CLogger.h"
8021
8022 #include "graphics/FontMetrics.h"
8023 #include "graphics/ShaderManager.h"
8024 #include "graphics/TextRenderer.h"
8025 #include "lib/ogl.h"
8026 #include "lib/timer.h"
8027 #include "lib/utf8.h"
8028 #include "ps/CConsole.h"
8029 #include "ps/Profile.h"
8030+#include "ps/Pyrogenesis.h"
8031 #include "renderer/Renderer.h"
8032
8033 #include <ctime>
8034 #include <iostream>
8035
8036 #include <boost/algorithm/string/replace.hpp>
8037
8038 CStrW g_UniqueLogPostfix;
8039 static const double RENDER_TIMEOUT = 10.0; // seconds before messages are deleted
8040 static const double RENDER_TIMEOUT_RATE = 10.0; // number of timed-out messages deleted per second
8041 static const size_t RENDER_LIMIT = 20; // maximum messages on screen at once
8042
8043 // Set up a default logger that throws everything away, because that's
8044 // better than crashing. (This is particularly useful for unit tests which
8045 // don't care about any log output.)
8046 struct BlackHoleStreamBuf : public std::streambuf
8047 {
8048 } blackHoleStreamBuf;
8049 std::ostream blackHoleStream(&blackHoleStreamBuf);
8050 CLogger nullLogger(&blackHoleStream, &blackHoleStream, false, true);
8051
8052 CLogger* g_Logger = &nullLogger;
8053
8054 const char* html_header0 =
8055 "<!DOCTYPE html>\n"
8056 "<meta charset=\"utf-8\">\n"
8057 "<title>Pyrogenesis Log</title>\n"
8058 "<style>"
8059 "body { background: #eee; color: black; font-family: sans-serif; } "
8060 "p { background: white; margin: 3px 0 3px 0; } "
8061 ".error { color: red; } "
8062 ".warning { color: blue; }"
8063 "</style>\n"
8064 "<h2>0 A.D. (";
8065
8066 const char* html_header1 = "</h2>\n";
8067
8068 CLogger::CLogger()
8069 {
8070 OsPath mainlogPath(psLogDir() / (L"mainlog" + g_UniqueLogPostfix + L".html"));
8071 m_MainLog = new std::ofstream(OsString(mainlogPath).c_str(), std::ofstream::out | std::ofstream::trunc);
8072 debug_printf("Writing the mainlog at %s\n", mainlogPath.string8().c_str());
8073
8074 OsPath interestinglogPath(psLogDir() / (L"interestinglog" + g_UniqueLogPostfix + L".html"));
8075 m_InterestingLog = new std::ofstream(OsString(interestinglogPath).c_str(), std::ofstream::out | std::ofstream::trunc);
8076
8077 m_OwnsStreams = true;
8078 m_UseDebugPrintf = true;
8079
8080 Init();
8081 }
8082
8083 CLogger::CLogger(std::ostream* mainLog, std::ostream* interestingLog, bool takeOwnership, bool useDebugPrintf)
8084 {
8085 m_MainLog = mainLog;
8086 m_InterestingLog = interestingLog;
8087 m_OwnsStreams = takeOwnership;
8088 m_UseDebugPrintf = useDebugPrintf;
8089
8090 Init();
8091 }
8092
8093 void CLogger::Init()
8094 {
8095 m_RenderLastEraseTime = -1.0;
8096 // this is called too early to allow us to call timer_Time(),
8097 // so we'll fill in the initial value later
8098
8099 m_NumberOfMessages = 0;
8100 m_NumberOfErrors = 0;
8101 m_NumberOfWarnings = 0;
8102
8103 *m_MainLog << html_header0 << engine_version << ") Main log" << html_header1;
8104 *m_InterestingLog << html_header0 << engine_version << ") Main log (warnings and errors only)" << html_header1;
8105 }
8106
8107 CLogger::~CLogger()
8108 {
8109 char buffer[128];
8110 sprintf_s(buffer, ARRAY_SIZE(buffer), " with %d message(s), %d error(s) and %d warning(s).", m_NumberOfMessages,m_NumberOfErrors,m_NumberOfWarnings);
8111
8112 time_t t = time(NULL);
8113 struct tm* now = localtime(&t);
8114 char currentDate[17];
8115 sprintf_s(currentDate, ARRAY_SIZE(currentDate), "%04d-%02d-%02d", 1900+now->tm_year, 1+now->tm_mon, now->tm_mday);
8116 char currentTime[10];
8117 sprintf_s(currentTime, ARRAY_SIZE(currentTime), "%02d:%02d:%02d", now->tm_hour, now->tm_min, now->tm_sec);
8118
8119 //Write closing text
8120
8121 *m_MainLog << "<p>Engine exited successfully on " << currentDate;
8122 *m_MainLog << " at " << currentTime << buffer << "</p>\n";
8123
8124 *m_InterestingLog << "<p>Engine exited successfully on " << currentDate;
8125 *m_InterestingLog << " at " << currentTime << buffer << "</p>\n";
8126
8127 if (m_OwnsStreams)
8128 {
8129 SAFE_DELETE(m_InterestingLog);
8130 SAFE_DELETE(m_MainLog);
8131 }
8132 }
8133
8134 static std::string ToHTML(const char* message)
8135 {
8136 std::string cmessage = message;
8137 boost::algorithm::replace_all(cmessage, "&", "&");
8138 boost::algorithm::replace_all(cmessage, "<", "<");
8139 return cmessage;
8140 }
8141
8142 void CLogger::WriteMessage(const char* message, bool doRender = false)
8143 {
8144 std::string cmessage = ToHTML(message);
8145
8146 std::lock_guard<std::mutex> lock(m_Mutex);
8147
8148 ++m_NumberOfMessages;
8149 // if (m_UseDebugPrintf)
8150 // debug_printf("MESSAGE: %s\n", message);
8151
8152 *m_MainLog << "<p>" << cmessage << "</p>\n";
8153 m_MainLog->flush();
8154
8155 if (doRender)
8156 {
8157 if (g_Console) g_Console->InsertMessage(std::string("INFO: ") + message);
8158
8159 PushRenderMessage(Normal, message);
8160 }
8161 }
8162
8163 void CLogger::WriteError(const char* message)
8164 {
8165 std::string cmessage = ToHTML(message);
8166
8167 std::lock_guard<std::mutex> lock(m_Mutex);
8168
8169 ++m_NumberOfErrors;
8170 if (m_UseDebugPrintf)
8171 debug_printf("ERROR: %.16000s\n", message);
8172
8173 if (g_Console) g_Console->InsertMessage(std::string("ERROR: ") + message);
8174 *m_InterestingLog << "<p class=\"error\">ERROR: " << cmessage << "</p>\n";
8175 m_InterestingLog->flush();
8176
8177 *m_MainLog << "<p class=\"error\">ERROR: " << cmessage << "</p>\n";
8178 m_MainLog->flush();
8179
8180 PushRenderMessage(Error, message);
8181 }
8182
8183 void CLogger::WriteWarning(const char* message)
8184 {
8185 std::string cmessage = ToHTML(message);
8186
8187 std::lock_guard<std::mutex> lock(m_Mutex);
8188
8189 ++m_NumberOfWarnings;
8190 if (m_UseDebugPrintf)
8191 debug_printf("WARNING: %s\n", message);
8192
8193 if (g_Console) g_Console->InsertMessage(std::string("WARNING: ") + message);
8194 *m_InterestingLog << "<p class=\"warning\">WARNING: " << cmessage << "</p>\n";
8195 m_InterestingLog->flush();
8196
8197 *m_MainLog << "<p class=\"warning\">WARNING: " << cmessage << "</p>\n";
8198 m_MainLog->flush();
8199
8200 PushRenderMessage(Warning, message);
8201 }
8202
8203 void CLogger::Render()
8204 {
8205 PROFILE3_GPU("logger");
8206
8207 CleanupRenderQueue();
8208
8209 CStrIntern font_name("mono-stroke-10");
8210 CFontMetrics font(font_name);
8211 int lineSpacing = font.GetLineSpacing();
8212
8213 CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
8214 textTech->BeginPass();
8215
8216 CTextRenderer textRenderer(textTech->GetShader());
8217 textRenderer.Font(font_name);
8218 textRenderer.Color(1.0f, 1.0f, 1.0f);
8219
8220 // Offset by an extra 35px vertically to avoid the top bar.
8221 textRenderer.Translate(4.0f, 35.0f + lineSpacing, 0.0f);
8222
8223 // (Lock must come after loading the CFont, since that might log error messages
8224 // and attempt to lock the mutex recursively which is forbidden)
8225 std::lock_guard<std::mutex> lock(m_Mutex);
8226
8227 for (const RenderedMessage& msg : m_RenderMessages)
8228 {
8229 const char* type;
8230 if (msg.method == Normal)
8231 {
8232 type = "info";
8233 textRenderer.Color(0.0f, 0.8f, 0.0f);
8234 }
8235 else if (msg.method == Warning)
8236 {
8237 type = "warning";
8238 textRenderer.Color(1.0f, 1.0f, 0.0f);
8239 }
8240 else
8241 {
8242 type = "error";
8243 textRenderer.Color(1.0f, 0.0f, 0.0f);
8244 }
8245
8246 CMatrix3D savedTransform = textRenderer.GetTransform();
8247
8248 textRenderer.PrintfAdvance(L"[%8.3f] %hs: ", msg.time, type);
8249 // Display the actual message in white so it's more readable
8250 textRenderer.Color(1.0f, 1.0f, 1.0f);
8251 textRenderer.Put(0.0f, 0.0f, msg.message.c_str());
8252
8253 textRenderer.SetTransform(savedTransform);
8254
8255 textRenderer.Translate(0.0f, (float)lineSpacing, 0.0f);
8256 }
8257
8258 textRenderer.Render();
8259
8260 textTech->EndPass();
8261 }
8262
8263 void CLogger::PushRenderMessage(ELogMethod method, const char* message)
8264 {
8265 double now = timer_Time();
8266
8267 // Add each message line separately
8268 const char* pos = message;
8269 const char* eol;
8270 while ((eol = strchr(pos, '\n')) != NULL)
8271 {
8272 if (eol != pos)
8273 {
8274 RenderedMessage r = { method, now, std::string(pos, eol) };
8275 m_RenderMessages.push_back(r);
8276 }
8277 pos = eol + 1;
8278 }
8279 // Add the last line, if we didn't end on a \n
8280 if (*pos != '\0')
8281 {
8282 RenderedMessage r = { method, now, std::string(pos) };
8283 m_RenderMessages.push_back(r);
8284 }
8285 }
8286
8287 void CLogger::CleanupRenderQueue()
8288 {
8289 std::lock_guard<std::mutex> lock(m_Mutex);
8290
8291 if (m_RenderMessages.empty())
8292 return;
8293
8294 double now = timer_Time();
8295
8296 // Initialise the timer on the first call (since we can't do it in the ctor)
8297 if (m_RenderLastEraseTime == -1.0)
8298 m_RenderLastEraseTime = now;
8299
8300 // Delete old messages, approximately at the given rate limit (and at most one per frame)
8301 if (now - m_RenderLastEraseTime > 1.0/RENDER_TIMEOUT_RATE)
8302 {
8303 if (m_RenderMessages[0].time + RENDER_TIMEOUT < now)
8304 {
8305 m_RenderMessages.pop_front();
8306 m_RenderLastEraseTime = now;
8307 }
8308 }
8309
8310 // If there's still too many then delete the oldest
8311 if (m_RenderMessages.size() > RENDER_LIMIT)
8312 m_RenderMessages.erase(m_RenderMessages.begin(), m_RenderMessages.end() - RENDER_LIMIT);
8313 }
8314
8315 TestLogger::TestLogger()
8316 {
8317 m_OldLogger = g_Logger;
8318 g_Logger = new CLogger(&m_Stream, &blackHoleStream, false, false);
8319 }
8320
8321 TestLogger::~TestLogger()
8322 {
8323 delete g_Logger;
8324 g_Logger = m_OldLogger;
8325 }
8326
8327 std::string TestLogger::GetOutput()
8328 {
8329 return m_Stream.str();
8330 }
8331
8332 TestStdoutLogger::TestStdoutLogger()
8333 {
8334 m_OldLogger = g_Logger;
8335 g_Logger = new CLogger(&std::cout, &blackHoleStream, false, false);
8336 }
8337
8338 TestStdoutLogger::~TestStdoutLogger()
8339 {
8340 delete g_Logger;
8341 g_Logger = m_OldLogger;
8342 }
8343Index: source/ps/GameSetup/GameSetup.cpp
8344===================================================================
8345--- source/ps/GameSetup/GameSetup.cpp (revision 23275)
8346+++ source/ps/GameSetup/GameSetup.cpp (working copy)
8347@@ -1,1680 +1,1660 @@
8348 /* Copyright (C) 2019 Wildfire Games.
8349 * This file is part of 0 A.D.
8350 *
8351 * 0 A.D. is free software: you can redistribute it and/or modify
8352 * it under the terms of the GNU General Public License as published by
8353 * the Free Software Foundation, either version 2 of the License, or
8354 * (at your option) any later version.
8355 *
8356 * 0 A.D. is distributed in the hope that it will be useful,
8357 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8358 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8359 * GNU General Public License for more details.
8360 *
8361 * You should have received a copy of the GNU General Public License
8362 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
8363 */
8364
8365 #include "precompiled.h"
8366
8367 #include "lib/app_hooks.h"
8368 #include "lib/config2.h"
8369 #include "lib/input.h"
8370 #include "lib/ogl.h"
8371 #include "lib/timer.h"
8372 #include "lib/external_libraries/libsdl.h"
8373 #include "lib/file/common/file_stats.h"
8374 #include "lib/res/h_mgr.h"
8375 #include "lib/res/graphics/cursor.h"
8376-#include "lib/sysdep/cursor.h"
8377
8378 #include "graphics/CinemaManager.h"
8379 #include "graphics/FontMetrics.h"
8380 #include "graphics/GameView.h"
8381 #include "graphics/LightEnv.h"
8382 #include "graphics/MapReader.h"
8383 #include "graphics/MaterialManager.h"
8384 #include "graphics/TerrainTextureManager.h"
8385 #include "gui/CGUI.h"
8386 #include "gui/GUIManager.h"
8387 #include "i18n/L10n.h"
8388 #include "maths/MathUtil.h"
8389 #include "network/NetServer.h"
8390 #include "network/NetClient.h"
8391 #include "network/NetMessage.h"
8392 #include "network/NetMessages.h"
8393
8394 #include "ps/CConsole.h"
8395 #include "ps/CLogger.h"
8396 #include "ps/ConfigDB.h"
8397 #include "ps/Filesystem.h"
8398 #include "ps/Game.h"
8399 #include "ps/GameSetup/Atlas.h"
8400 #include "ps/GameSetup/GameSetup.h"
8401 #include "ps/GameSetup/Paths.h"
8402 #include "ps/GameSetup/Config.h"
8403 #include "ps/GameSetup/CmdLineArgs.h"
8404 #include "ps/GameSetup/HWDetect.h"
8405 #include "ps/Globals.h"
8406 #include "ps/Hotkey.h"
8407 #include "ps/Joystick.h"
8408 #include "ps/Loader.h"
8409 #include "ps/Mod.h"
8410 #include "ps/ModIo.h"
8411 #include "ps/Profile.h"
8412 #include "ps/ProfileViewer.h"
8413 #include "ps/Profiler2.h"
8414 #include "ps/Pyrogenesis.h" // psSetLogDir
8415 #include "ps/scripting/JSInterface_Console.h"
8416 #include "ps/TouchInput.h"
8417 #include "ps/UserReport.h"
8418 #include "ps/Util.h"
8419 #include "ps/VideoMode.h"
8420 #include "ps/VisualReplay.h"
8421 #include "ps/World.h"
8422
8423 #include "renderer/Renderer.h"
8424 #include "renderer/VertexBufferManager.h"
8425 #include "renderer/ModelRenderer.h"
8426 #include "scriptinterface/ScriptInterface.h"
8427 #include "scriptinterface/ScriptStats.h"
8428 #include "scriptinterface/ScriptConversions.h"
8429 #include "scriptinterface/ScriptRuntime.h"
8430 #include "simulation2/Simulation2.h"
8431 #include "lobby/IXmppClient.h"
8432 #include "soundmanager/scripting/JSInterface_Sound.h"
8433 #include "soundmanager/ISoundManager.h"
8434 #include "tools/atlas/GameInterface/GameLoop.h"
8435 #include "tools/atlas/GameInterface/View.h"
8436
8437-#if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets
8438-#define MUST_INIT_X11 1
8439-#include <X11/Xlib.h>
8440-#else
8441-#define MUST_INIT_X11 0
8442-#endif
8443-
8444 extern void RestartEngine();
8445
8446 #include <iostream>
8447
8448 #include <boost/algorithm/string/classification.hpp>
8449 #include <boost/algorithm/string/split.hpp>
8450
8451 ERROR_GROUP(System);
8452 ERROR_TYPE(System, SDLInitFailed);
8453 ERROR_TYPE(System, VmodeFailed);
8454 ERROR_TYPE(System, RequiredExtensionsMissing);
8455
8456 bool g_DoRenderGui = true;
8457 bool g_DoRenderLogger = true;
8458 bool g_DoRenderCursor = true;
8459
8460 shared_ptr<ScriptRuntime> g_ScriptRuntime;
8461
8462 static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code
8463
8464 bool g_InDevelopmentCopy;
8465 bool g_CheckedIfInDevelopmentCopy = false;
8466
8467 static void SetTextureQuality(int quality)
8468 {
8469 int q_flags;
8470 GLint filter;
8471
8472 retry:
8473 // keep this in sync with SANE_TEX_QUALITY_DEFAULT
8474 switch(quality)
8475 {
8476 // worst quality
8477 case 0:
8478 q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP;
8479 filter = GL_NEAREST;
8480 break;
8481 // [perf] add bilinear filtering
8482 case 1:
8483 q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP;
8484 filter = GL_LINEAR;
8485 break;
8486 // [vmem] no longer reduce resolution
8487 case 2:
8488 q_flags = OGL_TEX_HALF_BPP;
8489 filter = GL_LINEAR;
8490 break;
8491 // [vmem] add mipmaps
8492 case 3:
8493 q_flags = OGL_TEX_HALF_BPP;
8494 filter = GL_NEAREST_MIPMAP_LINEAR;
8495 break;
8496 // [perf] better filtering
8497 case 4:
8498 q_flags = OGL_TEX_HALF_BPP;
8499 filter = GL_LINEAR_MIPMAP_LINEAR;
8500 break;
8501 // [vmem] no longer reduce bpp
8502 case SANE_TEX_QUALITY_DEFAULT:
8503 q_flags = OGL_TEX_FULL_QUALITY;
8504 filter = GL_LINEAR_MIPMAP_LINEAR;
8505 break;
8506 // [perf] add anisotropy
8507 case 6:
8508 // TODO: add anisotropic filtering
8509 q_flags = OGL_TEX_FULL_QUALITY;
8510 filter = GL_LINEAR_MIPMAP_LINEAR;
8511 break;
8512 // invalid
8513 default:
8514 debug_warn(L"SetTextureQuality: invalid quality");
8515 quality = SANE_TEX_QUALITY_DEFAULT;
8516 // careful: recursion doesn't work and we don't want to duplicate
8517 // the "sane" default values.
8518 goto retry;
8519 }
8520
8521 ogl_tex_set_defaults(q_flags, filter);
8522 }
8523
8524
8525 //----------------------------------------------------------------------------
8526 // GUI integration
8527 //----------------------------------------------------------------------------
8528
8529 // display progress / description in loading screen
8530 void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task)
8531 {
8532 const ScriptInterface& scriptInterface = *(g_GUI->GetActiveGUI()->GetScriptInterface());
8533 JSContext* cx = scriptInterface.GetContext();
8534 JSAutoRequest rq(cx);
8535
8536 JS::AutoValueVector paramData(cx);
8537
8538 paramData.append(JS::NumberValue(percent));
8539
8540 JS::RootedValue valPendingTask(cx);
8541 scriptInterface.ToJSVal(cx, &valPendingTask, pending_task);
8542 paramData.append(valPendingTask);
8543
8544 g_GUI->SendEventToAll("GameLoadProgress", paramData);
8545 }
8546
8547 bool ShouldRender()
8548 {
8549 return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen());
8550 }
8551
8552
8553 void Render()
8554 {
8555 // Do not render if not focused while in fullscreen or minimised,
8556 // as that triggers a difficult-to-reproduce crash on some graphic cards.
8557 if (!ShouldRender())
8558 return;
8559
8560 PROFILE3("render");
8561
8562 ogl_WarnIfError();
8563
8564 g_Profiler2.RecordGPUFrameStart();
8565
8566 ogl_WarnIfError();
8567
8568 // prepare before starting the renderer frame
8569 if (g_Game && g_Game->IsGameStarted())
8570 g_Game->GetView()->BeginFrame();
8571
8572 if (g_Game)
8573 g_Renderer.SetSimulation(g_Game->GetSimulation2());
8574
8575 // start new frame
8576 g_Renderer.BeginFrame();
8577
8578 ogl_WarnIfError();
8579
8580 if (g_Game && g_Game->IsGameStarted())
8581 g_Game->GetView()->Render();
8582
8583 ogl_WarnIfError();
8584
8585 g_Renderer.RenderTextOverlays();
8586
8587 // If we're in Atlas game view, render special tools
8588 if (g_AtlasGameLoop && g_AtlasGameLoop->view)
8589 {
8590 g_AtlasGameLoop->view->DrawCinemaPathTool();
8591 ogl_WarnIfError();
8592 }
8593
8594 if (g_Game && g_Game->IsGameStarted())
8595 g_Game->GetView()->GetCinema()->Render();
8596
8597 ogl_WarnIfError();
8598
8599 if (g_DoRenderGui)
8600 g_GUI->Draw();
8601
8602 ogl_WarnIfError();
8603
8604 // If we're in Atlas game view, render special overlays (e.g. editor bandbox)
8605 if (g_AtlasGameLoop && g_AtlasGameLoop->view)
8606 {
8607 g_AtlasGameLoop->view->DrawOverlays();
8608 ogl_WarnIfError();
8609 }
8610
8611 // Text:
8612
8613 glDisable(GL_DEPTH_TEST);
8614
8615 g_Console->Render();
8616
8617 ogl_WarnIfError();
8618
8619 if (g_DoRenderLogger)
8620 g_Logger->Render();
8621
8622 ogl_WarnIfError();
8623
8624 // Profile information
8625
8626 g_ProfileViewer.RenderProfile();
8627
8628 ogl_WarnIfError();
8629
8630 // Draw the cursor (or set the Windows cursor, on Windows)
8631 if (g_DoRenderCursor)
8632 {
8633 PROFILE3_GPU("cursor");
8634 CStrW cursorName = g_CursorName;
8635 if (cursorName.empty())
8636 {
8637 cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y, g_GuiScale, false);
8638 }
8639 else
8640 {
8641 bool forceGL = false;
8642 CFG_GET_VAL("nohwcursor", forceGL);
8643
8644 #if CONFIG2_GLES
8645 #warning TODO: implement cursors for GLES
8646 #else
8647 // set up transform for GL cursor
8648 glMatrixMode(GL_PROJECTION);
8649 glPushMatrix();
8650 glLoadIdentity();
8651 glMatrixMode(GL_MODELVIEW);
8652 glPushMatrix();
8653 glLoadIdentity();
8654 CMatrix3D transform;
8655 transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f);
8656 glLoadMatrixf(&transform._11);
8657 #endif
8658
8659 #if OS_ANDROID
8660 #warning TODO: cursors for Android
8661 #else
8662 if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y, g_GuiScale, forceGL) < 0)
8663 LOGWARNING("Failed to draw cursor '%s'", utf8_from_wstring(cursorName));
8664 #endif
8665
8666 #if CONFIG2_GLES
8667 #warning TODO: implement cursors for GLES
8668 #else
8669 // restore transform
8670 glMatrixMode(GL_PROJECTION);
8671 glPopMatrix();
8672 glMatrixMode(GL_MODELVIEW);
8673 glPopMatrix();
8674 #endif
8675 }
8676 }
8677
8678 glEnable(GL_DEPTH_TEST);
8679
8680 g_Renderer.EndFrame();
8681
8682 PROFILE2_ATTR("draw calls: %d", (int)g_Renderer.GetStats().m_DrawCalls);
8683 PROFILE2_ATTR("terrain tris: %d", (int)g_Renderer.GetStats().m_TerrainTris);
8684 PROFILE2_ATTR("water tris: %d", (int)g_Renderer.GetStats().m_WaterTris);
8685 PROFILE2_ATTR("model tris: %d", (int)g_Renderer.GetStats().m_ModelTris);
8686 PROFILE2_ATTR("overlay tris: %d", (int)g_Renderer.GetStats().m_OverlayTris);
8687 PROFILE2_ATTR("blend splats: %d", (int)g_Renderer.GetStats().m_BlendSplats);
8688 PROFILE2_ATTR("particles: %d", (int)g_Renderer.GetStats().m_Particles);
8689
8690 ogl_WarnIfError();
8691
8692 g_Profiler2.RecordGPUFrameEnd();
8693
8694 ogl_WarnIfError();
8695 }
8696
8697 ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags))
8698 {
8699 // If we're fullscreen, then sometimes (at least on some particular drivers on Linux)
8700 // displaying the error dialog hangs the desktop since the dialog box is behind the
8701 // fullscreen window. So we just force the game to windowed mode before displaying the dialog.
8702 // (But only if we're in the main thread, and not if we're being reentrant.)
8703 if (ThreadUtil::IsMainThread())
8704 {
8705 static bool reentering = false;
8706 if (!reentering)
8707 {
8708 reentering = true;
8709 g_VideoMode.SetFullscreen(false);
8710 reentering = false;
8711 }
8712 }
8713
8714 // We don't actually implement the error display here, so return appropriately
8715 return ERI_NOT_IMPLEMENTED;
8716 }
8717
8718 const std::vector<CStr>& GetMods(const CmdLineArgs& args, int flags)
8719 {
8720 const bool init_mods = (flags & INIT_MODS) == INIT_MODS;
8721 const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod");
8722 const bool add_public = (flags & INIT_MODS_PUBLIC) == INIT_MODS_PUBLIC;
8723
8724 if (!init_mods)
8725 {
8726 // Add the user mod if it should be present
8727 if (add_user && (g_modsLoaded.empty() || g_modsLoaded.back() != "user"))
8728 g_modsLoaded.push_back("user");
8729
8730 return g_modsLoaded;
8731 }
8732
8733 g_modsLoaded = args.GetMultiple("mod");
8734
8735 if (add_public)
8736 g_modsLoaded.insert(g_modsLoaded.begin(), "public");
8737
8738 g_modsLoaded.insert(g_modsLoaded.begin(), "mod");
8739
8740 // Add the user mod if not explicitly disabled or we have a dev copy so
8741 // that saved files end up in version control and not in the user mod.
8742 if (add_user)
8743 g_modsLoaded.push_back("user");
8744
8745 return g_modsLoaded;
8746 }
8747
8748 void MountMods(const Paths& paths, const std::vector<CStr>& mods)
8749 {
8750 OsPath modPath = paths.RData()/"mods";
8751 OsPath modUserPath = paths.UserData()/"mods";
8752 for (size_t i = 0; i < mods.size(); ++i)
8753 {
8754 size_t priority = (i+1)*2; // mods are higher priority than regular mountings, which default to priority 0
8755 size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_REPLACEABLE;
8756 size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST;
8757
8758 OsPath modName(mods[i]);
8759 if (InDevelopmentCopy())
8760 {
8761 // We are running a dev copy, so only mount mods in the user mod path
8762 // if the mod does not exist in the data path.
8763 if (DirectoryExists(modPath / modName/""))
8764 g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
8765 else
8766 g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority);
8767 }
8768 else
8769 {
8770 g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
8771 // Ensure that user modified files are loaded, if they are present
8772 g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority+1);
8773 }
8774 }
8775 }
8776
8777 static void InitVfs(const CmdLineArgs& args, int flags)
8778 {
8779 TIMER(L"InitVfs");
8780
8781 const bool setup_error = (flags & INIT_HAVE_DISPLAY_ERROR) == 0;
8782
8783 const Paths paths(args);
8784
8785 OsPath logs(paths.Logs());
8786 CreateDirectories(logs, 0700);
8787
8788 psSetLogDir(logs);
8789 // desired location for crashlog is now known. update AppHooks ASAP
8790 // (particularly before the following error-prone operations):
8791 AppHooks hooks = {0};
8792 hooks.bundle_logs = psBundleLogs;
8793 hooks.get_log_dir = psLogDir;
8794 if (setup_error)
8795 hooks.display_error = psDisplayError;
8796 app_hooks_update(&hooks);
8797
8798 g_VFS = CreateVfs();
8799
8800 const OsPath readonlyConfig = paths.RData()/"config"/"";
8801 g_VFS->Mount(L"config/", readonlyConfig);
8802
8803 // Engine localization files.
8804 g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/"");
8805
8806 MountMods(paths, GetMods(args, flags));
8807
8808 // We mount these dirs last as otherwise writing could result in files being placed in a mod's dir.
8809 g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"");
8810 g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH);
8811 // Mounting with highest priority, so that a mod supplied user.cfg is harmless
8812 g_VFS->Mount(L"config/", readonlyConfig, 0, (size_t)-1);
8813 if(readonlyConfig != paths.Config())
8814 g_VFS->Mount(L"config/", paths.Config(), 0, (size_t)-1);
8815
8816 g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads)
8817
8818 // note: don't bother with g_VFS->TextRepresentation - directories
8819 // haven't yet been populated and are empty.
8820 }
8821
8822
8823 static void InitPs(bool setup_gui, const CStrW& gui_page, ScriptInterface* srcScriptInterface, JS::HandleValue initData)
8824 {
8825 {
8826 // console
8827 TIMER(L"ps_console");
8828
8829 g_Console->UpdateScreenSize(g_xres, g_yres);
8830
8831 // Calculate and store the line spacing
8832 CFontMetrics font(CStrIntern(CONSOLE_FONT));
8833 g_Console->m_iFontHeight = font.GetLineSpacing();
8834 g_Console->m_iFontWidth = font.GetCharacterWidth(L'C');
8835 g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth);
8836 // Offset by an arbitrary amount, to make it fit more nicely
8837 g_Console->m_iFontOffset = 7;
8838
8839 double blinkRate = 0.5;
8840 CFG_GET_VAL("gui.cursorblinkrate", blinkRate);
8841 g_Console->SetCursorBlinkRate(blinkRate);
8842 }
8843
8844 // hotkeys
8845 {
8846 TIMER(L"ps_lang_hotkeys");
8847 LoadHotkeys();
8848 }
8849
8850 if (!setup_gui)
8851 {
8852 // We do actually need *some* kind of GUI loaded, so use the
8853 // (currently empty) Atlas one
8854 g_GUI->SwitchPage(L"page_atlas.xml", srcScriptInterface, initData);
8855 return;
8856 }
8857
8858 // GUI uses VFS, so this must come after VFS init.
8859 g_GUI->SwitchPage(gui_page, srcScriptInterface, initData);
8860 }
8861
8862 void InitPsAutostart(bool networked, JS::HandleValue attrs)
8863 {
8864 // The GUI has not been initialized yet, so use the simulation scriptinterface for this variable
8865 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
8866 JSContext* cx = scriptInterface.GetContext();
8867 JSAutoRequest rq(cx);
8868
8869 JS::RootedValue playerAssignments(cx);
8870 ScriptInterface::CreateObject(cx, &playerAssignments);
8871
8872 if (!networked)
8873 {
8874 JS::RootedValue localPlayer(cx);
8875 ScriptInterface::CreateObject(cx, &localPlayer, "player", g_Game->GetPlayerID());
8876 scriptInterface.SetProperty(playerAssignments, "local", localPlayer);
8877 }
8878
8879 JS::RootedValue sessionInitData(cx);
8880
8881 ScriptInterface::CreateObject(
8882 cx,
8883 &sessionInitData,
8884 "attribs", attrs,
8885 "playerAssignments", playerAssignments);
8886
8887 InitPs(true, L"page_loading.xml", &scriptInterface, sessionInitData);
8888 }
8889
8890
8891 void InitInput()
8892 {
8893 g_Joystick.Initialise();
8894
8895 // register input handlers
8896 // This stack is constructed so the first added, will be the last
8897 // one called. This is important, because each of the handlers
8898 // has the potential to block events to go further down
8899 // in the chain. I.e. the last one in the list added, is the
8900 // only handler that can block all messages before they are
8901 // processed.
8902 in_add_handler(game_view_handler);
8903
8904 in_add_handler(CProfileViewer::InputThunk);
8905
8906 in_add_handler(conInputHandler);
8907
8908 in_add_handler(HotkeyInputHandler);
8909
8910 // gui_handler needs to be registered after (i.e. called before!) the
8911 // hotkey handler so that input boxes can be typed in without
8912 // setting off hotkeys.
8913 in_add_handler(gui_handler);
8914
8915 in_add_handler(touch_input_handler);
8916
8917 // must be registered after (called before) the GUI which relies on these globals
8918 in_add_handler(GlobalsInputHandler);
8919
8920 // Should be called first, this updates our hotkey press state
8921 // so that js calls to HotkeyIsPressed are synched with events.
8922 in_add_handler(HotkeyStateChange);
8923 }
8924
8925
8926 static void ShutdownPs()
8927 {
8928 SAFE_DELETE(g_GUI);
8929
8930 UnloadHotkeys();
8931
8932 // disable the special Windows cursor, or free textures for OGL cursors
8933 cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y, 1.0, false);
8934 }
8935
8936
8937 static void InitRenderer()
8938 {
8939 TIMER(L"InitRenderer");
8940
8941 if(g_NoGLS3TC)
8942 ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE);
8943 if(g_NoGLAutoMipmap)
8944 ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE);
8945
8946 // create renderer
8947 new CRenderer;
8948
8949 g_RenderingOptions.ReadConfig();
8950
8951 // set renderer options from command line options - NOVBO must be set before opening the renderer
8952 // and init them in the ConfigDB when needed
8953 g_RenderingOptions.SetNoVBO(g_NoGLVBO);
8954 g_RenderingOptions.SetShadows(g_Shadows);
8955 g_ConfigDB.SetValueBool(CFG_SYSTEM, "shadows", g_Shadows);
8956
8957 g_RenderingOptions.SetWaterEffects(g_WaterEffects);
8958 g_ConfigDB.SetValueBool(CFG_SYSTEM, "watereffects", g_WaterEffects);
8959 g_RenderingOptions.SetWaterFancyEffects(g_WaterFancyEffects);
8960 g_ConfigDB.SetValueBool(CFG_SYSTEM, "waterfancyeffects", g_WaterFancyEffects);
8961 g_RenderingOptions.SetWaterRealDepth(g_WaterRealDepth);
8962 g_ConfigDB.SetValueBool(CFG_SYSTEM, "waterrealdepth", g_WaterRealDepth);
8963 g_RenderingOptions.SetWaterReflection(g_WaterReflection);
8964 g_ConfigDB.SetValueBool(CFG_SYSTEM, "waterreflection", g_WaterReflection);
8965 g_RenderingOptions.SetWaterRefraction(g_WaterRefraction);
8966 g_ConfigDB.SetValueBool(CFG_SYSTEM, "waterrefraction", g_WaterRefraction);
8967 g_RenderingOptions.SetWaterShadows(g_WaterShadows);
8968 g_ConfigDB.SetValueBool(CFG_SYSTEM, "watershadows", g_WaterShadows);
8969
8970 g_RenderingOptions.SetRenderPath(RenderPathEnum::FromString(g_RenderPath));
8971 g_RenderingOptions.SetShadowPCF(g_ShadowPCF);
8972 g_ConfigDB.SetValueBool(CFG_SYSTEM, "shadowpcf", g_ShadowPCF);
8973 g_RenderingOptions.SetParticles(g_Particles);
8974 g_ConfigDB.SetValueBool(CFG_SYSTEM, "particles", g_Particles);
8975 g_RenderingOptions.SetFog(g_Fog);
8976 g_ConfigDB.SetValueBool(CFG_SYSTEM, "fog", g_Fog);
8977 g_RenderingOptions.SetSilhouettes(g_Silhouettes);
8978 g_ConfigDB.SetValueBool(CFG_SYSTEM, "silhouettes", g_Silhouettes);
8979 g_RenderingOptions.SetShowSky(g_ShowSky);
8980 g_ConfigDB.SetValueBool(CFG_SYSTEM, "showsky", g_ShowSky);
8981 g_RenderingOptions.SetPreferGLSL(g_PreferGLSL);
8982 g_ConfigDB.SetValueBool(CFG_SYSTEM, "preferglsl", g_PreferGLSL);
8983 g_RenderingOptions.SetPostProc(g_PostProc);
8984 g_ConfigDB.SetValueBool(CFG_SYSTEM, "postproc", g_PostProc);
8985 g_RenderingOptions.SetSmoothLOS(g_SmoothLOS);
8986 g_ConfigDB.SetValueBool(CFG_SYSTEM, "smoothlos", g_SmoothLOS);
8987
8988 // create terrain related stuff
8989 new CTerrainTextureManager;
8990
8991 g_Renderer.Open(g_xres, g_yres);
8992
8993 // Setup lighting environment. Since the Renderer accesses the
8994 // lighting environment through a pointer, this has to be done before
8995 // the first Frame.
8996 g_Renderer.SetLightEnv(&g_LightEnv);
8997
8998 // I haven't seen the camera affecting GUI rendering and such, but the
8999 // viewport has to be updated according to the video mode
9000 SViewPort vp;
9001 vp.m_X = 0;
9002 vp.m_Y = 0;
9003 vp.m_Width = g_xres;
9004 vp.m_Height = g_yres;
9005 g_Renderer.SetViewport(vp);
9006
9007 ColorActivateFastImpl();
9008 ModelRenderer::Init();
9009 }
9010
9011 static void InitSDL()
9012 {
9013 #if OS_LINUX
9014 // In fullscreen mode when SDL is compiled with DGA support, the mouse
9015 // sensitivity often appears to be unusably wrong (typically too low).
9016 // (This seems to be reported almost exclusively on Ubuntu, but can be
9017 // reproduced on Gentoo after explicitly enabling DGA.)
9018 // Disabling the DGA mouse appears to fix that problem, and doesn't
9019 // have any obvious negative effects.
9020 setenv("SDL_VIDEO_X11_DGAMOUSE", "0", 0);
9021 #endif
9022
9023 if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0)
9024 {
9025 LOGERROR("SDL library initialization failed: %s", SDL_GetError());
9026 throw PSERROR_System_SDLInitFailed();
9027 }
9028 atexit(SDL_Quit);
9029
9030 // Text input is active by default, disable it until it is actually needed.
9031 SDL_StopTextInput();
9032
9033 #if OS_MACOSX
9034 // Some Mac mice only have one button, so they can't right-click
9035 // but SDL2 can emulate that with Ctrl+Click
9036 bool macMouse = false;
9037 CFG_GET_VAL("macmouse", macMouse);
9038 SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, macMouse ? "1" : "0");
9039 #endif
9040 }
9041
9042 static void ShutdownSDL()
9043 {
9044 SDL_Quit();
9045- sys_cursor_reset();
9046 }
9047
9048
9049 void EndGame()
9050 {
9051 SAFE_DELETE(g_NetClient);
9052 SAFE_DELETE(g_NetServer);
9053 SAFE_DELETE(g_Game);
9054
9055 if (CRenderer::IsInitialised())
9056 {
9057 ISoundManager::CloseGame();
9058 g_Renderer.ResetState();
9059 }
9060 }
9061
9062 void Shutdown(int flags)
9063 {
9064 const bool hasRenderer = CRenderer::IsInitialised();
9065
9066 if ((flags & SHUTDOWN_FROM_CONFIG))
9067 goto from_config;
9068
9069 EndGame();
9070
9071 SAFE_DELETE(g_XmppClient);
9072
9073 SAFE_DELETE(g_ModIo);
9074
9075 ShutdownPs();
9076
9077 TIMER_BEGIN(L"shutdown TexMan");
9078 delete &g_TexMan;
9079 TIMER_END(L"shutdown TexMan");
9080
9081 if (hasRenderer)
9082 {
9083 TIMER_BEGIN(L"shutdown Renderer");
9084 g_Renderer.~CRenderer();
9085 g_VBMan.Shutdown();
9086 TIMER_END(L"shutdown Renderer");
9087 }
9088
9089 g_Profiler2.ShutdownGPU();
9090
9091 // Free cursors before shutting down SDL, as they may depend on SDL.
9092 cursor_shutdown();
9093
9094 TIMER_BEGIN(L"shutdown SDL");
9095 ShutdownSDL();
9096 TIMER_END(L"shutdown SDL");
9097
9098 if (hasRenderer)
9099 g_VideoMode.Shutdown();
9100
9101 TIMER_BEGIN(L"shutdown UserReporter");
9102 g_UserReporter.Deinitialize();
9103 TIMER_END(L"shutdown UserReporter");
9104
9105 // Cleanup curl now that g_ModIo and g_UserReporter have been shutdown.
9106 curl_global_cleanup();
9107
9108 delete &g_L10n;
9109
9110 from_config:
9111 TIMER_BEGIN(L"shutdown ConfigDB");
9112 delete &g_ConfigDB;
9113 TIMER_END(L"shutdown ConfigDB");
9114
9115 SAFE_DELETE(g_Console);
9116
9117 // This is needed to ensure that no callbacks from the JSAPI try to use
9118 // the profiler when it's already destructed
9119 g_ScriptRuntime.reset();
9120
9121 // resource
9122 // first shut down all resource owners, and then the handle manager.
9123 TIMER_BEGIN(L"resource modules");
9124
9125 ISoundManager::SetEnabled(false);
9126
9127 g_VFS.reset();
9128
9129 // this forcibly frees all open handles (thus preventing real leaks),
9130 // and makes further access to h_mgr impossible.
9131 h_mgr_shutdown();
9132
9133 file_stats_dump();
9134
9135 TIMER_END(L"resource modules");
9136
9137 TIMER_BEGIN(L"shutdown misc");
9138 timer_DisplayClientTotals();
9139
9140 CNetHost::Deinitialize();
9141
9142 // should be last, since the above use them
9143 SAFE_DELETE(g_Logger);
9144 delete &g_Profiler;
9145 delete &g_ProfileViewer;
9146
9147 SAFE_DELETE(g_ScriptStatsTable);
9148 TIMER_END(L"shutdown misc");
9149 }
9150
9151 #if OS_UNIX
9152 static void FixLocales()
9153 {
9154 #if OS_MACOSX || OS_BSD
9155 // OS X requires a UTF-8 locale in LC_CTYPE so that *wprintf can handle
9156 // wide characters. Peculiarly the string "UTF-8" seems to be acceptable
9157 // despite not being a real locale, and it's conveniently language-agnostic,
9158 // so use that.
9159 setlocale(LC_CTYPE, "UTF-8");
9160 #endif
9161
9162
9163 // On misconfigured systems with incorrect locale settings, we'll die
9164 // with a C++ exception when some code (e.g. Boost) tries to use locales.
9165 // To avoid death, we'll detect the problem here and warn the user and
9166 // reset to the default C locale.
9167
9168
9169 // For informing the user of the problem, use the list of env vars that
9170 // glibc setlocale looks at. (LC_ALL is checked first, and LANG last.)
9171 const char* const LocaleEnvVars[] = {
9172 "LC_ALL",
9173 "LC_COLLATE",
9174 "LC_CTYPE",
9175 "LC_MONETARY",
9176 "LC_NUMERIC",
9177 "LC_TIME",
9178 "LC_MESSAGES",
9179 "LANG"
9180 };
9181
9182 try
9183 {
9184 // this constructor is similar to setlocale(LC_ALL, ""),
9185 // but instead of returning NULL, it throws runtime_error
9186 // when the first locale env variable found contains an invalid value
9187 std::locale("");
9188 }
9189 catch (std::runtime_error&)
9190 {
9191 LOGWARNING("Invalid locale settings");
9192
9193 for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++)
9194 {
9195 if (char* envval = getenv(LocaleEnvVars[i]))
9196 LOGWARNING(" %s=\"%s\"", LocaleEnvVars[i], envval);
9197 else
9198 LOGWARNING(" %s=\"(unset)\"", LocaleEnvVars[i]);
9199 }
9200
9201 // We should set LC_ALL since it overrides LANG
9202 if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1))
9203 debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable.");
9204 else
9205 LOGWARNING("Setting LC_ALL env variable to: %s", getenv("LC_ALL"));
9206 }
9207 }
9208 #else
9209 static void FixLocales()
9210 {
9211 // Do nothing on Windows
9212 }
9213 #endif
9214
9215 void EarlyInit()
9216 {
9217 // If you ever want to catch a particular allocation:
9218 //_CrtSetBreakAlloc(232647);
9219
9220 ThreadUtil::SetMainThread();
9221
9222 debug_SetThreadName("main");
9223 // add all debug_printf "tags" that we are interested in:
9224 debug_filter_add("TIMER");
9225
9226 timer_LatchStartTime();
9227
9228 // initialise profiler early so it can profile startup,
9229 // but only after LatchStartTime
9230- g_Profiler2.Initialise();
9231+ //g_Profiler2.Initialise();
9232
9233 FixLocales();
9234
9235- // Because we do GL calls from a secondary thread, Xlib needs to
9236- // be told to support multiple threads safely.
9237- // This is needed for Atlas, but we have to call it before any other
9238- // Xlib functions (e.g. the ones used when drawing the main menu
9239- // before launching Atlas)
9240-#if MUST_INIT_X11
9241- int status = XInitThreads();
9242- if (status == 0)
9243- debug_printf("Error enabling thread-safety via XInitThreads\n");
9244-#endif
9245-
9246 // Initialise the low-quality rand function
9247 srand(time(NULL)); // NOTE: this rand should *not* be used for simulation!
9248 }
9249
9250 bool Autostart(const CmdLineArgs& args);
9251
9252 /**
9253 * Returns true if the user has intended to start a visual replay from command line.
9254 */
9255 bool AutostartVisualReplay(const std::string& replayFile);
9256
9257 bool Init(const CmdLineArgs& args, int flags)
9258 {
9259 h_mgr_init();
9260
9261 // Do this as soon as possible, because it chdirs
9262 // and will mess up the error reporting if anything
9263 // crashes before the working directory is set.
9264 InitVfs(args, flags);
9265
9266 // This must come after VFS init, which sets the current directory
9267 // (required for finding our output log files).
9268 g_Logger = new CLogger;
9269
9270 new CProfileViewer;
9271 new CProfileManager; // before any script code
9272
9273 g_ScriptStatsTable = new CScriptStatsTable;
9274 g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
9275
9276 // Set up the console early, so that debugging
9277 // messages can be logged to it. (The console's size
9278 // and fonts are set later in InitPs())
9279 g_Console = new CConsole();
9280
9281 // g_ConfigDB, command line args, globals
9282 CONFIG_Init(args);
9283
9284 // Using a global object for the runtime is a workaround until Simulation and AI use
9285 // their own threads and also their own runtimes.
9286 const int runtimeSize = 384 * 1024 * 1024;
9287 const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024;
9288 g_ScriptRuntime = ScriptInterface::CreateRuntime(shared_ptr<ScriptRuntime>(), runtimeSize, heapGrowthBytesGCTrigger);
9289
9290 Mod::CacheEnabledModVersions(g_ScriptRuntime);
9291
9292 // Special command-line mode to dump the entity schemas instead of running the game.
9293 // (This must be done after loading VFS etc, but should be done before wasting time
9294 // on anything else.)
9295 if (args.Has("dumpSchema"))
9296 {
9297 CSimulation2 sim(NULL, g_ScriptRuntime, NULL);
9298 sim.LoadDefaultScripts();
9299 std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc);
9300 f << sim.GenerateSchema();
9301 std::cout << "Generated entity.rng\n";
9302 exit(0);
9303 }
9304
9305 CNetHost::Initialize();
9306
9307 #if CONFIG2_AUDIO
9308 if (!args.Has("autostart-nonvisual"))
9309 ISoundManager::CreateSoundManager();
9310 #endif
9311
9312 // Check if there are mods specified on the command line,
9313 // or if we already set the mods (~INIT_MODS),
9314 // else check if there are mods that should be loaded specified
9315 // in the config and load those (by aborting init and restarting
9316 // the engine).
9317 if (!args.Has("mod") && (flags & INIT_MODS) == INIT_MODS)
9318 {
9319 CStr modstring;
9320 CFG_GET_VAL("mod.enabledmods", modstring);
9321 if (!modstring.empty())
9322 {
9323 std::vector<CStr> mods;
9324 boost::split(mods, modstring, boost::is_any_of(" "), boost::token_compress_on);
9325 std::swap(g_modsLoaded, mods);
9326
9327 // Abort init and restart
9328 RestartEngine();
9329 return false;
9330 }
9331 }
9332
9333 new L10n;
9334
9335 // Optionally start profiler HTTP output automatically
9336 // (By default it's only enabled by a hotkey, for security/performance)
9337 bool profilerHTTPEnable = false;
9338 CFG_GET_VAL("profiler2.autoenable", profilerHTTPEnable);
9339 if (profilerHTTPEnable)
9340 g_Profiler2.EnableHTTP();
9341
9342 // Initialise everything except Win32 sockets (because our networking
9343 // system already inits those)
9344 curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32);
9345
9346 if (!g_Quickstart)
9347 g_UserReporter.Initialize(); // after config
9348
9349 PROFILE2_EVENT("Init finished");
9350 return true;
9351 }
9352
9353 void InitGraphics(const CmdLineArgs& args, int flags, const std::vector<CStr>& installedMods)
9354 {
9355 const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0;
9356
9357 if(setup_vmode)
9358 {
9359 InitSDL();
9360
9361 if (!g_VideoMode.InitSDL())
9362 throw PSERROR_System_VmodeFailed(); // abort startup
9363 }
9364
9365 RunHardwareDetection();
9366
9367 const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file
9368 SetTextureQuality(quality);
9369
9370 ogl_WarnIfError();
9371
9372 // Optionally start profiler GPU timings automatically
9373 // (By default it's only enabled by a hotkey, for performance/compatibility)
9374 bool profilerGPUEnable = false;
9375 CFG_GET_VAL("profiler2.autoenable", profilerGPUEnable);
9376 if (profilerGPUEnable)
9377 g_Profiler2.EnableGPU();
9378
9379 if(!g_Quickstart)
9380 {
9381 WriteSystemInfo();
9382 // note: no longer vfs_display here. it's dog-slow due to unbuffered
9383 // file output and very rarely needed.
9384 }
9385
9386 if(g_DisableAudio)
9387 ISoundManager::SetEnabled(false);
9388
9389 g_GUI = new CGUIManager();
9390
9391 // (must come after SetVideoMode, since it calls ogl_Init)
9392 if (ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL) != 0 // ARB
9393 && ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", NULL) != 0) // GLSL
9394 {
9395 DEBUG_DISPLAY_ERROR(
9396 L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders."
9397 L" In the future, the game will not support pre-shader graphics cards."
9398 L" You are advised to try installing newer drivers and/or upgrade your graphics card."
9399 L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734"
9400 );
9401 // TODO: actually quit once fixed function support is dropped
9402 }
9403
9404 const char* missing = ogl_HaveExtensions(0,
9405 "GL_ARB_multitexture",
9406 "GL_EXT_draw_range_elements",
9407 "GL_ARB_texture_env_combine",
9408 "GL_ARB_texture_env_dot3",
9409 NULL);
9410 if(missing)
9411 {
9412 wchar_t buf[500];
9413 swprintf_s(buf, ARRAY_SIZE(buf),
9414 L"The %hs extension doesn't appear to be available on your computer."
9415 L" The game may still work, though - you are welcome to try at your own risk."
9416 L" If not or it doesn't look right, upgrade your graphics card.",
9417 missing
9418 );
9419 DEBUG_DISPLAY_ERROR(buf);
9420 // TODO: i18n
9421 }
9422
9423 if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar"))
9424 {
9425 DEBUG_DISPLAY_ERROR(
9426 L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer."
9427 L" Shadows are not available and overall graphics quality might suffer."
9428 L" You are advised to try installing newer drivers and/or upgrade your graphics card.");
9429 g_Shadows = false;
9430 }
9431
9432 ogl_WarnIfError();
9433 InitRenderer();
9434
9435 InitInput();
9436
9437 ogl_WarnIfError();
9438
9439 // TODO: Is this the best place for this?
9440 if (VfsDirectoryExists(L"maps/"))
9441 CXeromyces::AddValidator(g_VFS, "map", "maps/scenario.rng");
9442
9443 try
9444 {
9445 if (!AutostartVisualReplay(args.Get("replay-visual")) && !Autostart(args))
9446 {
9447 const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
9448 // We only want to display the splash screen at startup
9449 shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetScriptInterface();
9450 JSContext* cx = scriptInterface->GetContext();
9451 JSAutoRequest rq(cx);
9452 JS::RootedValue data(cx);
9453 if (g_GUI)
9454 {
9455 ScriptInterface::CreateObject(cx, &data, "isStartup", true);
9456 if (!installedMods.empty())
9457 scriptInterface->SetProperty(data, "installedMods", installedMods);
9458 }
9459 InitPs(setup_gui, installedMods.empty() ? L"page_pregame.xml" : L"page_modmod.xml", g_GUI->GetScriptInterface().get(), data);
9460 }
9461 }
9462 catch (PSERROR_Game_World_MapLoadFailed& e)
9463 {
9464 // Map Loading failed
9465
9466 // Start the engine so we have a GUI
9467 InitPs(true, L"page_pregame.xml", NULL, JS::UndefinedHandleValue);
9468
9469 // Call script function to do the actual work
9470 // (delete game data, switch GUI page, show error, etc.)
9471 CancelLoad(CStr(e.what()).FromUTF8());
9472 }
9473 }
9474
9475 void InitNonVisual(const CmdLineArgs& args)
9476 {
9477 // Need some stuff for terrain movement costs:
9478 // (TODO: this ought to be independent of any graphics code)
9479 new CTerrainTextureManager;
9480 g_TexMan.LoadTerrainTextures();
9481 Autostart(args);
9482 }
9483
9484 void RenderGui(bool RenderingState)
9485 {
9486 g_DoRenderGui = RenderingState;
9487 }
9488
9489 void RenderLogger(bool RenderingState)
9490 {
9491 g_DoRenderLogger = RenderingState;
9492 }
9493
9494 void RenderCursor(bool RenderingState)
9495 {
9496 g_DoRenderCursor = RenderingState;
9497 }
9498
9499 /**
9500 * Temporarily loads a scenario map and retrieves the "ScriptSettings" JSON
9501 * data from it.
9502 * The scenario map format is used for scenario and skirmish map types (random
9503 * games do not use a "map" (format) but a small JavaScript program which
9504 * creates a map on the fly). It contains a section to initialize the game
9505 * setup screen.
9506 * @param mapPath Absolute path (from VFS root) to the map file to peek in.
9507 * @return ScriptSettings in JSON format extracted from the map.
9508 */
9509 CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath)
9510 {
9511 CXeromyces mapFile;
9512 const char *pathToSettings[] =
9513 {
9514 "Scenario", "ScriptSettings", "" // Path to JSON data in map
9515 };
9516
9517 Status loadResult = mapFile.Load(g_VFS, mapPath);
9518
9519 if (INFO::OK != loadResult)
9520 {
9521 LOGERROR("LoadSettingsOfScenarioMap: Unable to load map file '%s'", mapPath.string8());
9522 throw PSERROR_Game_World_MapLoadFailed("Unable to load map file, check the path for typos.");
9523 }
9524 XMBElement mapElement = mapFile.GetRoot();
9525
9526 // Select the ScriptSettings node in the map file...
9527 for (int i = 0; pathToSettings[i][0]; ++i)
9528 {
9529 int childId = mapFile.GetElementID(pathToSettings[i]);
9530
9531 XMBElementList nodes = mapElement.GetChildNodes();
9532 auto it = std::find_if(nodes.begin(), nodes.end(), [&childId](const XMBElement& child) {
9533 return child.GetNodeName() == childId;
9534 });
9535
9536 if (it != nodes.end())
9537 mapElement = *it;
9538 }
9539 // ... they contain a JSON document to initialize the game setup
9540 // screen
9541 return mapElement.GetText();
9542 }
9543
9544 /*
9545 * Command line options for autostart
9546 * (keep synchronized with binaries/system/readme.txt):
9547 *
9548 * -autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME;
9549 * TYPEDIR is skirmishes, scenarios, or random
9550 * -autostart-seed=SEED sets randomization seed value (default 0, use -1 for random)
9551 * -autostart-ai=PLAYER:AI sets the AI for PLAYER (e.g. 2:petra)
9552 * -autostart-aidiff=PLAYER:DIFF sets the DIFFiculty of PLAYER's AI
9553 * (0: sandbox, 5: very hard)
9554 * -autostart-aiseed=AISEED sets the seed used for the AI random
9555 * generator (default 0, use -1 for random)
9556 * -autostart-player=NUMBER sets the playerID in non-networked games (default 1, use -1 for observer)
9557 * -autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV
9558 * (skirmish and random maps only)
9559 * -autostart-team=PLAYER:TEAM sets the team for PLAYER (e.g. 2:2).
9560 * -autostart-ceasefire=NUM sets a ceasefire duration NUM
9561 * (default 0 minutes)
9562 * -autostart-nonvisual disable any graphics and sounds
9563 * -autostart-victory=SCRIPTNAME sets the victory conditions with SCRIPTNAME
9564 * located in simulation/data/settings/victory_conditions/
9565 * (default conquest). When the first given SCRIPTNAME is
9566 * "endless", no victory conditions will apply.
9567 * -autostart-wonderduration=NUM sets the victory duration NUM for wonder victory condition
9568 * (default 10 minutes)
9569 * -autostart-relicduration=NUM sets the victory duration NUM for relic victory condition
9570 * (default 10 minutes)
9571 * -autostart-reliccount=NUM sets the number of relics for relic victory condition
9572 * (default 2 relics)
9573 * -autostart-disable-replay disable saving of replays
9574 *
9575 * Multiplayer:
9576 * -autostart-playername=NAME sets local player NAME (default 'anonymous')
9577 * -autostart-host sets multiplayer host mode
9578 * -autostart-host-players=NUMBER sets NUMBER of human players for multiplayer
9579 * game (default 2)
9580 * -autostart-client=IP sets multiplayer client to join host at
9581 * given IP address
9582 * Random maps only:
9583 * -autostart-size=TILES sets random map size in TILES (default 192)
9584 * -autostart-players=NUMBER sets NUMBER of players on random map
9585 * (default 2)
9586 *
9587 * Examples:
9588 * 1) "Bob" will host a 2 player game on the Arcadia map:
9589 * -autostart="scenarios/Arcadia" -autostart-host -autostart-host-players=2 -autostart-playername="Bob"
9590 * "Alice" joins the match as player 2:
9591 * -autostart="scenarios/Arcadia" -autostart-client=127.0.0.1 -autostart-playername="Alice"
9592 * The players use the developer overlay to control players.
9593 *
9594 * 2) Load Alpine Lakes random map with random seed, 2 players (Athens and Britons), and player 2 is PetraBot:
9595 * -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra
9596 *
9597 * 3) Observe the PetraBot on a triggerscript map:
9598 * -autostart="random/jebel_barkal" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=1:petra -autostart-ai=2:petra -autostart-player=-1
9599 */
9600 bool Autostart(const CmdLineArgs& args)
9601 {
9602 CStr autoStartName = args.Get("autostart");
9603
9604 if (autoStartName.empty())
9605 return false;
9606
9607 g_Game = new CGame(!args.Has("autostart-disable-replay"));
9608
9609 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
9610 JSContext* cx = scriptInterface.GetContext();
9611 JSAutoRequest rq(cx);
9612
9613 JS::RootedValue attrs(cx);
9614 JS::RootedValue settings(cx);
9615 JS::RootedValue playerData(cx);
9616
9617 ScriptInterface::CreateObject(cx, &attrs);
9618 ScriptInterface::CreateObject(cx, &settings);
9619 ScriptInterface::CreateArray(cx, &playerData);
9620
9621 // The directory in front of the actual map name indicates which type
9622 // of map is being loaded. Drawback of this approach is the association
9623 // of map types and folders is hard-coded, but benefits are:
9624 // - No need to pass the map type via command line separately
9625 // - Prevents mixing up of scenarios and skirmish maps to some degree
9626 Path mapPath = Path(autoStartName);
9627 std::wstring mapDirectory = mapPath.Parent().Filename().string();
9628 std::string mapType;
9629
9630 if (mapDirectory == L"random")
9631 {
9632 // Random map definition will be loaded from JSON file, so we need to parse it
9633 std::wstring scriptPath = L"maps/" + autoStartName.FromUTF8() + L".json";
9634 JS::RootedValue scriptData(cx);
9635 scriptInterface.ReadJSONFile(scriptPath, &scriptData);
9636 if (!scriptData.isUndefined() && scriptInterface.GetProperty(scriptData, "settings", &settings))
9637 {
9638 // JSON loaded ok - copy script name over to game attributes
9639 std::wstring scriptFile;
9640 scriptInterface.GetProperty(settings, "Script", scriptFile);
9641 scriptInterface.SetProperty(attrs, "script", scriptFile); // RMS filename
9642 }
9643 else
9644 {
9645 // Problem with JSON file
9646 LOGERROR("Autostart: Error reading random map script '%s'", utf8_from_wstring(scriptPath));
9647 throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details.");
9648 }
9649
9650 // Get optional map size argument (default 192)
9651 uint mapSize = 192;
9652 if (args.Has("autostart-size"))
9653 {
9654 CStr size = args.Get("autostart-size");
9655 mapSize = size.ToUInt();
9656 }
9657
9658 scriptInterface.SetProperty(settings, "Size", mapSize); // Random map size (in patches)
9659
9660 // Get optional number of players (default 2)
9661 size_t numPlayers = 2;
9662 if (args.Has("autostart-players"))
9663 {
9664 CStr num = args.Get("autostart-players");
9665 numPlayers = num.ToUInt();
9666 }
9667
9668 // Set up player data
9669 for (size_t i = 0; i < numPlayers; ++i)
9670 {
9671 JS::RootedValue player(cx);
9672
9673 // We could load player_defaults.json here, but that would complicate the logic
9674 // even more and autostart is only intended for developers anyway
9675 ScriptInterface::CreateObject(cx, &player, "Civ", "athen");
9676
9677 scriptInterface.SetPropertyInt(playerData, i, player);
9678 }
9679 mapType = "random";
9680 }
9681 else if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
9682 {
9683 // Initialize general settings from the map data so some values
9684 // (e.g. name of map) are always present, even when autostart is
9685 // partially configured
9686 CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml");
9687 scriptInterface.ParseJSON(mapSettingsJSON, &settings);
9688
9689 // Initialize the playerData array being modified by autostart
9690 // with the real map data, so sensible values are present:
9691 scriptInterface.GetProperty(settings, "PlayerData", &playerData);
9692
9693 if (mapDirectory == L"scenarios")
9694 mapType = "scenario";
9695 else
9696 mapType = "skirmish";
9697 }
9698 else
9699 {
9700 LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory));
9701 throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types.");
9702 }
9703
9704 scriptInterface.SetProperty(attrs, "mapType", mapType);
9705 scriptInterface.SetProperty(attrs, "map", "maps/" + autoStartName);
9706 scriptInterface.SetProperty(settings, "mapType", mapType);
9707 scriptInterface.SetProperty(settings, "CheatsEnabled", true);
9708
9709 // The seed is used for both random map generation and simulation
9710 u32 seed = 0;
9711 if (args.Has("autostart-seed"))
9712 {
9713 CStr seedArg = args.Get("autostart-seed");
9714 if (seedArg == "-1")
9715 seed = rand();
9716 else
9717 seed = seedArg.ToULong();
9718 }
9719 scriptInterface.SetProperty(settings, "Seed", seed);
9720
9721 // Set seed for AIs
9722 u32 aiseed = 0;
9723 if (args.Has("autostart-aiseed"))
9724 {
9725 CStr seedArg = args.Get("autostart-aiseed");
9726 if (seedArg == "-1")
9727 aiseed = rand();
9728 else
9729 aiseed = seedArg.ToULong();
9730 }
9731 scriptInterface.SetProperty(settings, "AISeed", aiseed);
9732
9733 // Set player data for AIs
9734 // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }
9735 // or = { PlayerData: [ null, { AI: ... }, ... ] } when gaia set
9736 int offset = 1;
9737 JS::RootedValue player(cx);
9738 if (scriptInterface.GetPropertyInt(playerData, 0, &player) && player.isNull())
9739 offset = 0;
9740
9741 // Set teams
9742 if (args.Has("autostart-team"))
9743 {
9744 std::vector<CStr> civArgs = args.GetMultiple("autostart-team");
9745 for (size_t i = 0; i < civArgs.size(); ++i)
9746 {
9747 int playerID = civArgs[i].BeforeFirst(":").ToInt();
9748
9749 // Instead of overwriting existing player data, modify the array
9750 JS::RootedValue player(cx);
9751 if (!scriptInterface.GetPropertyInt(playerData, playerID-offset, &player) || player.isUndefined())
9752 {
9753 if (mapDirectory == L"skirmishes")
9754 {
9755 // playerID is certainly bigger than this map player number
9756 LOGWARNING("Autostart: Invalid player %d in autostart-team option", playerID);
9757 continue;
9758 }
9759 ScriptInterface::CreateObject(cx, &player);
9760 }
9761
9762 int teamID = civArgs[i].AfterFirst(":").ToInt() - 1;
9763 scriptInterface.SetProperty(player, "Team", teamID);
9764 scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
9765 }
9766 }
9767
9768 int ceasefire = 0;
9769 if (args.Has("autostart-ceasefire"))
9770 ceasefire = args.Get("autostart-ceasefire").ToInt();
9771 scriptInterface.SetProperty(settings, "Ceasefire", ceasefire);
9772
9773 if (args.Has("autostart-ai"))
9774 {
9775 std::vector<CStr> aiArgs = args.GetMultiple("autostart-ai");
9776 for (size_t i = 0; i < aiArgs.size(); ++i)
9777 {
9778 int playerID = aiArgs[i].BeforeFirst(":").ToInt();
9779
9780 // Instead of overwriting existing player data, modify the array
9781 JS::RootedValue player(cx);
9782 if (!scriptInterface.GetPropertyInt(playerData, playerID-offset, &player) || player.isUndefined())
9783 {
9784 if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
9785 {
9786 // playerID is certainly bigger than this map player number
9787 LOGWARNING("Autostart: Invalid player %d in autostart-ai option", playerID);
9788 continue;
9789 }
9790 ScriptInterface::CreateObject(cx, &player);
9791 }
9792
9793 scriptInterface.SetProperty(player, "AI", aiArgs[i].AfterFirst(":"));
9794 scriptInterface.SetProperty(player, "AIDiff", 3);
9795 scriptInterface.SetProperty(player, "AIBehavior", "balanced");
9796 scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
9797 }
9798 }
9799 // Set AI difficulty
9800 if (args.Has("autostart-aidiff"))
9801 {
9802 std::vector<CStr> civArgs = args.GetMultiple("autostart-aidiff");
9803 for (size_t i = 0; i < civArgs.size(); ++i)
9804 {
9805 int playerID = civArgs[i].BeforeFirst(":").ToInt();
9806
9807 // Instead of overwriting existing player data, modify the array
9808 JS::RootedValue player(cx);
9809 if (!scriptInterface.GetPropertyInt(playerData, playerID-offset, &player) || player.isUndefined())
9810 {
9811 if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
9812 {
9813 // playerID is certainly bigger than this map player number
9814 LOGWARNING("Autostart: Invalid player %d in autostart-aidiff option", playerID);
9815 continue;
9816 }
9817 ScriptInterface::CreateObject(cx, &player);
9818 }
9819
9820 scriptInterface.SetProperty(player, "AIDiff", civArgs[i].AfterFirst(":").ToInt());
9821 scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
9822 }
9823 }
9824 // Set player data for Civs
9825 if (args.Has("autostart-civ"))
9826 {
9827 if (mapDirectory != L"scenarios")
9828 {
9829 std::vector<CStr> civArgs = args.GetMultiple("autostart-civ");
9830 for (size_t i = 0; i < civArgs.size(); ++i)
9831 {
9832 int playerID = civArgs[i].BeforeFirst(":").ToInt();
9833
9834 // Instead of overwriting existing player data, modify the array
9835 JS::RootedValue player(cx);
9836 if (!scriptInterface.GetPropertyInt(playerData, playerID-offset, &player) || player.isUndefined())
9837 {
9838 if (mapDirectory == L"skirmishes")
9839 {
9840 // playerID is certainly bigger than this map player number
9841 LOGWARNING("Autostart: Invalid player %d in autostart-civ option", playerID);
9842 continue;
9843 }
9844 ScriptInterface::CreateObject(cx, &player);
9845 }
9846
9847 scriptInterface.SetProperty(player, "Civ", civArgs[i].AfterFirst(":"));
9848 scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
9849 }
9850 }
9851 else
9852 LOGWARNING("Autostart: Option 'autostart-civ' is invalid for scenarios");
9853 }
9854
9855 // Add player data to map settings
9856 scriptInterface.SetProperty(settings, "PlayerData", playerData);
9857
9858 // Add map settings to game attributes
9859 scriptInterface.SetProperty(attrs, "settings", settings);
9860
9861 // Get optional playername
9862 CStrW userName = L"anonymous";
9863 if (args.Has("autostart-playername"))
9864 userName = args.Get("autostart-playername").FromUTF8();
9865
9866 // Add additional scripts to the TriggerScripts property
9867 std::vector<CStrW> triggerScriptsVector;
9868 JS::RootedValue triggerScripts(cx);
9869
9870 if (scriptInterface.HasProperty(settings, "TriggerScripts"))
9871 {
9872 scriptInterface.GetProperty(settings, "TriggerScripts", &triggerScripts);
9873 FromJSVal_vector(cx, triggerScripts, triggerScriptsVector);
9874 }
9875
9876 if (!CRenderer::IsInitialised())
9877 {
9878 CStr nonVisualScript = "scripts/NonVisualTrigger.js";
9879 triggerScriptsVector.push_back(nonVisualScript.FromUTF8());
9880 }
9881
9882 std::vector<CStr> victoryConditions(1, "conquest");
9883 if (args.Has("autostart-victory"))
9884 victoryConditions = args.GetMultiple("autostart-victory");
9885
9886 if (victoryConditions.size() == 1 && victoryConditions[0] == "endless")
9887 victoryConditions.clear();
9888
9889 scriptInterface.SetProperty(settings, "VictoryConditions", victoryConditions);
9890
9891 for (const CStr& victory : victoryConditions)
9892 {
9893 JS::RootedValue scriptData(cx);
9894 JS::RootedValue data(cx);
9895 JS::RootedValue victoryScripts(cx);
9896
9897 CStrW scriptPath = L"simulation/data/settings/victory_conditions/" + victory.FromUTF8() + L".json";
9898 scriptInterface.ReadJSONFile(scriptPath, &scriptData);
9899 if (!scriptData.isUndefined() && scriptInterface.GetProperty(scriptData, "Data", &data) && !data.isUndefined()
9900 && scriptInterface.GetProperty(data, "Scripts", &victoryScripts) && !victoryScripts.isUndefined())
9901 {
9902 std::vector<CStrW> victoryScriptsVector;
9903 FromJSVal_vector(cx, victoryScripts, victoryScriptsVector);
9904 triggerScriptsVector.insert(triggerScriptsVector.end(), victoryScriptsVector.begin(), victoryScriptsVector.end());
9905 }
9906 else
9907 {
9908 LOGERROR("Autostart: Error reading victory script '%s'", utf8_from_wstring(scriptPath));
9909 throw PSERROR_Game_World_MapLoadFailed("Error reading victory script.\nCheck application log for details.");
9910 }
9911 }
9912
9913 ToJSVal_vector(cx, &triggerScripts, triggerScriptsVector);
9914 scriptInterface.SetProperty(settings, "TriggerScripts", triggerScripts);
9915
9916 int wonderDuration = 10;
9917 if (args.Has("autostart-wonderduration"))
9918 wonderDuration = args.Get("autostart-wonderduration").ToInt();
9919 scriptInterface.SetProperty(settings, "WonderDuration", wonderDuration);
9920
9921 int relicDuration = 10;
9922 if (args.Has("autostart-relicduration"))
9923 relicDuration = args.Get("autostart-relicduration").ToInt();
9924 scriptInterface.SetProperty(settings, "RelicDuration", relicDuration);
9925
9926 int relicCount = 2;
9927 if (args.Has("autostart-reliccount"))
9928 relicCount = args.Get("autostart-reliccount").ToInt();
9929 scriptInterface.SetProperty(settings, "RelicCount", relicCount);
9930
9931 if (args.Has("autostart-host"))
9932 {
9933 InitPsAutostart(true, attrs);
9934
9935 size_t maxPlayers = 2;
9936 if (args.Has("autostart-host-players"))
9937 maxPlayers = args.Get("autostart-host-players").ToUInt();
9938
9939 g_NetServer = new CNetServer(false, maxPlayers);
9940
9941 g_NetServer->UpdateGameAttributes(&attrs, scriptInterface);
9942
9943 bool ok = g_NetServer->SetupConnection(PS_DEFAULT_PORT);
9944 ENSURE(ok);
9945
9946 g_NetClient = new CNetClient(g_Game, true);
9947 g_NetClient->SetUserName(userName);
9948 g_NetClient->SetupConnection("127.0.0.1", PS_DEFAULT_PORT, nullptr);
9949 }
9950 else if (args.Has("autostart-client"))
9951 {
9952 InitPsAutostart(true, attrs);
9953
9954 g_NetClient = new CNetClient(g_Game, false);
9955 g_NetClient->SetUserName(userName);
9956
9957 CStr ip = args.Get("autostart-client");
9958 if (ip.empty())
9959 ip = "127.0.0.1";
9960
9961 bool ok = g_NetClient->SetupConnection(ip, PS_DEFAULT_PORT, nullptr);
9962 ENSURE(ok);
9963 }
9964 else
9965 {
9966 g_Game->SetPlayerID(args.Has("autostart-player") ? args.Get("autostart-player").ToInt() : 1);
9967
9968 g_Game->StartGame(&attrs, "");
9969
9970 if (CRenderer::IsInitialised())
9971 {
9972 InitPsAutostart(false, attrs);
9973 }
9974 else
9975 {
9976 // TODO: Non progressive load can fail - need a decent way to handle this
9977 LDR_NonprogressiveLoad();
9978 ENSURE(g_Game->ReallyStartGame() == PSRETURN_OK);
9979 }
9980 }
9981
9982 return true;
9983 }
9984
9985 bool AutostartVisualReplay(const std::string& replayFile)
9986 {
9987 if (!FileExists(OsPath(replayFile)))
9988 return false;
9989
9990 g_Game = new CGame(false);
9991 g_Game->SetPlayerID(-1);
9992 g_Game->StartVisualReplay(replayFile);
9993
9994 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
9995 JSContext* cx = scriptInterface.GetContext();
9996 JSAutoRequest rq(cx);
9997 JS::RootedValue attrs(cx, g_Game->GetSimulation2()->GetInitAttributes());
9998
9999 InitPsAutostart(false, attrs);
10000
10001 return true;
10002 }
10003
10004 void CancelLoad(const CStrW& message)
10005 {
10006 shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
10007 JSContext* cx = pScriptInterface->GetContext();
10008 JSAutoRequest rq(cx);
10009
10010 JS::RootedValue global(cx, pScriptInterface->GetGlobalObject());
10011
10012 LDR_Cancel();
10013
10014 if (g_GUI &&
10015 g_GUI->GetPageCount() &&
10016 pScriptInterface->HasProperty(global, "cancelOnLoadGameError"))
10017 pScriptInterface->CallFunctionVoid(global, "cancelOnLoadGameError", message);
10018 }
10019
10020 bool InDevelopmentCopy()
10021 {
10022 if (!g_CheckedIfInDevelopmentCopy)
10023 {
10024 g_InDevelopmentCopy = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK);
10025 g_CheckedIfInDevelopmentCopy = true;
10026 }
10027 return g_InDevelopmentCopy;
10028 }
10029Index: source/ps/GameSetup/HWDetect.cpp
10030===================================================================
10031--- source/ps/GameSetup/HWDetect.cpp (revision 23275)
10032+++ source/ps/GameSetup/HWDetect.cpp (working copy)
10033@@ -1,778 +1,780 @@
10034 /* Copyright (C) 2019 Wildfire Games.
10035 * This file is part of 0 A.D.
10036 *
10037 * 0 A.D. is free software: you can redistribute it and/or modify
10038 * it under the terms of the GNU General Public License as published by
10039 * the Free Software Foundation, either version 2 of the License, or
10040 * (at your option) any later version.
10041 *
10042 * 0 A.D. is distributed in the hope that it will be useful,
10043 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10044 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10045 * GNU General Public License for more details.
10046 *
10047 * You should have received a copy of the GNU General Public License
10048 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
10049 */
10050
10051 #include "precompiled.h"
10052
10053 #include "scriptinterface/ScriptInterface.h"
10054
10055 #include "lib/ogl.h"
10056 #include "lib/snd.h"
10057 #include "lib/svn_revision.h"
10058 #include "lib/timer.h"
10059 #include "lib/utf8.h"
10060 #include "lib/external_libraries/libsdl.h"
10061 #include "lib/res/graphics/ogl_tex.h"
10062 #include "lib/posix/posix_utsname.h"
10063 #include "lib/sysdep/cpu.h"
10064 #include "lib/sysdep/gfx.h"
10065 #include "lib/sysdep/numa.h"
10066 #include "lib/sysdep/os_cpu.h"
10067 #if ARCH_X86_X64
10068 # include "lib/sysdep/arch/x86_x64/cache.h"
10069 # include "lib/sysdep/arch/x86_x64/topology.h"
10070 #endif
10071 #include "ps/CLogger.h"
10072 #include "ps/ConfigDB.h"
10073 #include "ps/Filesystem.h"
10074 #include "ps/GameSetup/Config.h"
10075 #include "ps/Profile.h"
10076 #include "ps/scripting/JSInterface_Debug.h"
10077 #include "ps/UserReport.h"
10078 #include "ps/VideoMode.h"
10079
10080 #ifdef SDL_VIDEO_DRIVER_X11
10081 #include <GL/glx.h>
10082 #include "SDL_syswm.h"
10083
10084 // Define the GLX_MESA_query_renderer macros if built with
10085 // an old Mesa (<10.0) that doesn't provide them
10086 #ifndef GLX_MESA_query_renderer
10087 #define GLX_MESA_query_renderer 1
10088 #define GLX_RENDERER_VENDOR_ID_MESA 0x8183
10089 #define GLX_RENDERER_DEVICE_ID_MESA 0x8184
10090 #define GLX_RENDERER_VERSION_MESA 0x8185
10091 #define GLX_RENDERER_ACCELERATED_MESA 0x8186
10092 #define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187
10093 #define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA 0x8188
10094 #define GLX_RENDERER_PREFERRED_PROFILE_MESA 0x8189
10095 #define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA 0x818A
10096 #define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B
10097 #define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA 0x818C
10098 #define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA 0x818D
10099 #define GLX_RENDERER_ID_MESA 0x818E
10100 #endif /* GLX_MESA_query_renderer */
10101
10102 #endif
10103
10104 static void ReportGLLimits(const ScriptInterface& scriptInterface, JS::HandleValue settings);
10105
10106 #if ARCH_X86_X64
10107 void ConvertCaches(const ScriptInterface& scriptInterface, x86_x64::IdxCache idxCache, JS::MutableHandleValue ret)
10108 {
10109 JSContext* cx = scriptInterface.GetContext();
10110 JSAutoRequest rq(cx);
10111
10112 ScriptInterface::CreateArray(cx, ret);
10113
10114 for (size_t idxLevel = 0; idxLevel < x86_x64::Cache::maxLevels; ++idxLevel)
10115 {
10116 const x86_x64::Cache* pcache = x86_x64::Caches(idxCache+idxLevel);
10117 if (pcache->m_Type == x86_x64::Cache::kNull || pcache->m_NumEntries == 0)
10118 continue;
10119
10120 JS::RootedValue cache(cx);
10121
10122 ScriptInterface::CreateObject(
10123 cx,
10124 &cache,
10125 "type", static_cast<u32>(pcache->m_Type),
10126 "level", static_cast<u32>(pcache->m_Level),
10127 "associativity", static_cast<u32>(pcache->m_Associativity),
10128 "linesize", static_cast<u32>(pcache->m_EntrySize),
10129 "sharedby", static_cast<u32>(pcache->m_SharedBy),
10130 "totalsize", static_cast<u32>(pcache->TotalSize()));
10131
10132 scriptInterface.SetPropertyInt(ret, idxLevel, cache);
10133 }
10134 }
10135
10136 void ConvertTLBs(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
10137 {
10138 JSContext* cx = scriptInterface.GetContext();
10139 JSAutoRequest rq(cx);
10140
10141 ScriptInterface::CreateArray(cx, ret);
10142
10143 for(size_t i = 0; ; i++)
10144 {
10145 const x86_x64::Cache* ptlb = x86_x64::Caches(x86_x64::TLB+i);
10146 if (!ptlb)
10147 break;
10148
10149 JS::RootedValue tlb(cx);
10150
10151 ScriptInterface::CreateObject(
10152 cx,
10153 &tlb,
10154 "type", static_cast<u32>(ptlb->m_Type),
10155 "level", static_cast<u32>(ptlb->m_Level),
10156 "associativity", static_cast<u32>(ptlb->m_Associativity),
10157 "pagesize", static_cast<u32>(ptlb->m_EntrySize),
10158 "entries", static_cast<u32>(ptlb->m_NumEntries));
10159
10160 scriptInterface.SetPropertyInt(ret, i, tlb);
10161 }
10162 }
10163 #endif
10164
10165 // The Set* functions will override the default behaviour, unless the user
10166 // has explicitly set a config variable to override that.
10167 // (TODO: This is an ugly abuse of the config system)
10168 static bool IsOverridden(const char* setting)
10169 {
10170 EConfigNamespace ns = g_ConfigDB.GetValueNamespace(CFG_COMMAND, setting);
10171 return !(ns == CFG_LAST || ns == CFG_DEFAULT);
10172 }
10173
10174 void SetDisableAudio(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10175 {
10176 g_DisableAudio = disabled;
10177 }
10178
10179 void SetDisableS3TC(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10180 {
10181 if (!IsOverridden("nos3tc"))
10182 ogl_tex_override(OGL_TEX_S3TC, disabled ? OGL_TEX_DISABLE : OGL_TEX_ENABLE);
10183 }
10184
10185 void SetDisableShadows(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10186 {
10187 if (!IsOverridden("shadows"))
10188 g_Shadows = !disabled;
10189 }
10190
10191 void SetDisableShadowPCF(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10192 {
10193 if (!IsOverridden("shadowpcf"))
10194 g_ShadowPCF = !disabled;
10195 }
10196
10197 void SetDisableAllWater(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10198 {
10199 if (!IsOverridden("watereffects"))
10200 g_WaterEffects = !disabled;
10201 if (!IsOverridden("waterfancyeffects"))
10202 g_WaterFancyEffects = !disabled;
10203 if (!IsOverridden("waterrealdepth"))
10204 g_WaterRealDepth = !disabled;
10205 if (!IsOverridden("waterrefraction"))
10206 g_WaterRefraction = !disabled;
10207 if (!IsOverridden("waterreflection"))
10208 g_WaterReflection = !disabled;
10209 if (!IsOverridden("watershadows"))
10210 g_WaterShadows = !disabled;
10211 }
10212
10213 void SetDisableFancyWater(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10214 {
10215 if (!IsOverridden("waterfancyeffects"))
10216 g_WaterFancyEffects = !disabled;
10217 if (!IsOverridden("waterrealdepth"))
10218 g_WaterRealDepth = !disabled;
10219 if (!IsOverridden("watershadows"))
10220 g_WaterShadows = !disabled;
10221 }
10222
10223 void SetEnableGLSL(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
10224 {
10225 if (!IsOverridden("preferglsl"))
10226 g_PreferGLSL = enabled;
10227 }
10228
10229 void SetEnablePostProc(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
10230 {
10231 if (!IsOverridden("postproc"))
10232 g_PostProc = enabled;
10233 }
10234
10235 void SetEnableSmoothLOS(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
10236 {
10237 if (!IsOverridden("smoothlos"))
10238 g_SmoothLOS = enabled;
10239 }
10240
10241 void SetRenderPath(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& renderpath)
10242 {
10243 g_RenderPath = renderpath;
10244 }
10245
10246 void RunHardwareDetection()
10247 {
10248 TIMER(L"RunHardwareDetection");
10249
10250 ScriptInterface scriptInterface("Engine", "HWDetect", g_ScriptRuntime);
10251 JSContext* cx = scriptInterface.GetContext();
10252 JSAutoRequest rq(cx);
10253
10254 JSI_Debug::RegisterScriptFunctions(scriptInterface); // Engine.DisplayErrorDialog
10255
10256 scriptInterface.RegisterFunction<void, bool, &SetDisableAudio>("SetDisableAudio");
10257 scriptInterface.RegisterFunction<void, bool, &SetDisableS3TC>("SetDisableS3TC");
10258 scriptInterface.RegisterFunction<void, bool, &SetDisableShadows>("SetDisableShadows");
10259 scriptInterface.RegisterFunction<void, bool, &SetDisableShadowPCF>("SetDisableShadowPCF");
10260 scriptInterface.RegisterFunction<void, bool, &SetDisableAllWater>("SetDisableAllWater");
10261 scriptInterface.RegisterFunction<void, bool, &SetDisableFancyWater>("SetDisableFancyWater");
10262 scriptInterface.RegisterFunction<void, bool, &SetEnableGLSL>("SetEnableGLSL");
10263 scriptInterface.RegisterFunction<void, bool, &SetEnablePostProc>("SetEnablePostProc");
10264 scriptInterface.RegisterFunction<void, bool, &SetEnableSmoothLOS>("SetEnableSmoothLOS");
10265 scriptInterface.RegisterFunction<void, std::string, &SetRenderPath>("SetRenderPath");
10266
10267 // Load the detection script:
10268
10269 const wchar_t* scriptName = L"hwdetect/hwdetect.js";
10270 CVFSFile file;
10271 if (file.Load(g_VFS, scriptName) != PSRETURN_OK)
10272 {
10273 LOGERROR("Failed to load hardware detection script");
10274 return;
10275 }
10276
10277 std::string code = file.DecodeUTF8(); // assume it's UTF-8
10278 scriptInterface.LoadScript(scriptName, code);
10279
10280 // Collect all the settings we'll pass to the script:
10281 // (We'll use this same data for the opt-in online reporting system, so it
10282 // includes some fields that aren't directly useful for the hwdetect script)
10283
10284 JS::RootedValue settings(cx);
10285 ScriptInterface::CreateObject(cx, &settings);
10286
10287 scriptInterface.SetProperty(settings, "os_unix", OS_UNIX);
10288 scriptInterface.SetProperty(settings, "os_bsd", OS_BSD);
10289 scriptInterface.SetProperty(settings, "os_linux", OS_LINUX);
10290 scriptInterface.SetProperty(settings, "os_android", OS_ANDROID);
10291 scriptInterface.SetProperty(settings, "os_macosx", OS_MACOSX);
10292 scriptInterface.SetProperty(settings, "os_win", OS_WIN);
10293
10294 scriptInterface.SetProperty(settings, "arch_ia32", ARCH_IA32);
10295 scriptInterface.SetProperty(settings, "arch_amd64", ARCH_AMD64);
10296 scriptInterface.SetProperty(settings, "arch_arm", ARCH_ARM);
10297 scriptInterface.SetProperty(settings, "arch_aarch64", ARCH_AARCH64);
10298
10299 #ifdef NDEBUG
10300 scriptInterface.SetProperty(settings, "build_debug", 0);
10301 #else
10302 scriptInterface.SetProperty(settings, "build_debug", 1);
10303 #endif
10304 scriptInterface.SetProperty(settings, "build_opengles", CONFIG2_GLES);
10305
10306 scriptInterface.SetProperty(settings, "build_datetime", std::string(__DATE__ " " __TIME__));
10307 scriptInterface.SetProperty(settings, "build_revision", std::wstring(svn_revision));
10308
10309 scriptInterface.SetProperty(settings, "build_msc", (int)MSC_VERSION);
10310 scriptInterface.SetProperty(settings, "build_icc", (int)ICC_VERSION);
10311 scriptInterface.SetProperty(settings, "build_gcc", (int)GCC_VERSION);
10312 scriptInterface.SetProperty(settings, "build_clang", (int)CLANG_VERSION);
10313
10314 scriptInterface.SetProperty(settings, "gfx_card", gfx::CardName());
10315 scriptInterface.SetProperty(settings, "gfx_drv_ver", gfx::DriverInfo());
10316
10317+#if CONFIG2_AUDIO
10318 scriptInterface.SetProperty(settings, "snd_card", snd_card);
10319 scriptInterface.SetProperty(settings, "snd_drv_ver", snd_drv_ver);
10320+#endif
10321
10322 ReportGLLimits(scriptInterface, settings);
10323
10324 scriptInterface.SetProperty(settings, "video_desktop_xres", g_VideoMode.GetDesktopXRes());
10325 scriptInterface.SetProperty(settings, "video_desktop_yres", g_VideoMode.GetDesktopYRes());
10326 scriptInterface.SetProperty(settings, "video_desktop_bpp", g_VideoMode.GetDesktopBPP());
10327 scriptInterface.SetProperty(settings, "video_desktop_freq", g_VideoMode.GetDesktopFreq());
10328
10329 struct utsname un;
10330 uname(&un);
10331 scriptInterface.SetProperty(settings, "uname_sysname", std::string(un.sysname));
10332 scriptInterface.SetProperty(settings, "uname_release", std::string(un.release));
10333 scriptInterface.SetProperty(settings, "uname_version", std::string(un.version));
10334 scriptInterface.SetProperty(settings, "uname_machine", std::string(un.machine));
10335
10336 #if OS_LINUX
10337 {
10338 std::ifstream ifs("/etc/lsb-release");
10339 if (ifs.good())
10340 {
10341 std::string str((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
10342 scriptInterface.SetProperty(settings, "linux_release", str);
10343 }
10344 }
10345 #endif
10346
10347 scriptInterface.SetProperty(settings, "cpu_identifier", std::string(cpu_IdentifierString()));
10348 scriptInterface.SetProperty(settings, "cpu_frequency", os_cpu_ClockFrequency());
10349 scriptInterface.SetProperty(settings, "cpu_pagesize", (u32)os_cpu_PageSize());
10350 scriptInterface.SetProperty(settings, "cpu_largepagesize", (u32)os_cpu_LargePageSize());
10351 scriptInterface.SetProperty(settings, "cpu_numprocs", (u32)os_cpu_NumProcessors());
10352 #if ARCH_X86_X64
10353 scriptInterface.SetProperty(settings, "cpu_numpackages", (u32)topology::NumPackages());
10354 scriptInterface.SetProperty(settings, "cpu_coresperpackage", (u32)topology::CoresPerPackage());
10355 scriptInterface.SetProperty(settings, "cpu_logicalpercore", (u32)topology::LogicalPerCore());
10356 scriptInterface.SetProperty(settings, "cpu_numcaches", (u32)topology::NumCaches());
10357 #endif
10358
10359 scriptInterface.SetProperty(settings, "numa_numnodes", (u32)numa_NumNodes());
10360 scriptInterface.SetProperty(settings, "numa_factor", numa_Factor());
10361 scriptInterface.SetProperty(settings, "numa_interleaved", numa_IsMemoryInterleaved());
10362
10363 scriptInterface.SetProperty(settings, "ram_total", (u32)os_cpu_MemorySize());
10364 scriptInterface.SetProperty(settings, "ram_total_os", (u32)os_cpu_QueryMemorySize());
10365
10366 #if ARCH_X86_X64
10367 scriptInterface.SetProperty(settings, "x86_vendor", (u32)x86_x64::Vendor());
10368 scriptInterface.SetProperty(settings, "x86_model", (u32)x86_x64::Model());
10369 scriptInterface.SetProperty(settings, "x86_family", (u32)x86_x64::Family());
10370
10371 u32 caps0, caps1, caps2, caps3;
10372 x86_x64::GetCapBits(&caps0, &caps1, &caps2, &caps3);
10373 scriptInterface.SetProperty(settings, "x86_caps[0]", caps0);
10374 scriptInterface.SetProperty(settings, "x86_caps[1]", caps1);
10375 scriptInterface.SetProperty(settings, "x86_caps[2]", caps2);
10376 scriptInterface.SetProperty(settings, "x86_caps[3]", caps3);
10377
10378 JS::RootedValue tmpVal(cx);
10379 ConvertCaches(scriptInterface, x86_x64::L1I, &tmpVal);
10380 scriptInterface.SetProperty(settings, "x86_icaches", tmpVal);
10381 ConvertCaches(scriptInterface, x86_x64::L1D, &tmpVal);
10382 scriptInterface.SetProperty(settings, "x86_dcaches", tmpVal);
10383 ConvertTLBs(scriptInterface, &tmpVal);
10384 scriptInterface.SetProperty(settings, "x86_tlbs", tmpVal);
10385 #endif
10386
10387 scriptInterface.SetProperty(settings, "timer_resolution", timer_Resolution());
10388
10389 // The version should be increased for every meaningful change.
10390 const int reportVersion = 12;
10391
10392 // Send the same data to the reporting system
10393 g_UserReporter.SubmitReport(
10394 "hwdetect",
10395 reportVersion,
10396 scriptInterface.StringifyJSON(&settings, false),
10397 scriptInterface.StringifyJSON(&settings, true));
10398
10399 // Run the detection script:
10400 JS::RootedValue global(cx, scriptInterface.GetGlobalObject());
10401 scriptInterface.CallFunctionVoid(global, "RunHardwareDetection", settings);
10402 }
10403
10404 static void ReportGLLimits(const ScriptInterface& scriptInterface, JS::HandleValue settings)
10405 {
10406 const char* errstr = "(error)";
10407
10408 #define INTEGER(id) do { \
10409 GLint i = -1; \
10410 glGetIntegerv(GL_##id, &i); \
10411 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10412 scriptInterface.SetProperty(settings, "GL_" #id, errstr); \
10413 else \
10414 scriptInterface.SetProperty(settings, "GL_" #id, i); \
10415 } while (false)
10416
10417 #define INTEGER2(id) do { \
10418 GLint i[2] = { -1, -1 }; \
10419 glGetIntegerv(GL_##id, i); \
10420 if (ogl_SquelchError(GL_INVALID_ENUM)) { \
10421 scriptInterface.SetProperty(settings, "GL_" #id "[0]", errstr); \
10422 scriptInterface.SetProperty(settings, "GL_" #id "[1]", errstr); \
10423 } else { \
10424 scriptInterface.SetProperty(settings, "GL_" #id "[0]", i[0]); \
10425 scriptInterface.SetProperty(settings, "GL_" #id "[1]", i[1]); \
10426 } \
10427 } while (false)
10428
10429 #define FLOAT(id) do { \
10430 GLfloat f = std::numeric_limits<GLfloat>::quiet_NaN(); \
10431 glGetFloatv(GL_##id, &f); \
10432 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10433 scriptInterface.SetProperty(settings, "GL_" #id, errstr); \
10434 else \
10435 scriptInterface.SetProperty(settings, "GL_" #id, f); \
10436 } while (false)
10437
10438 #define FLOAT2(id) do { \
10439 GLfloat f[2] = { std::numeric_limits<GLfloat>::quiet_NaN(), std::numeric_limits<GLfloat>::quiet_NaN() }; \
10440 glGetFloatv(GL_##id, f); \
10441 if (ogl_SquelchError(GL_INVALID_ENUM)) { \
10442 scriptInterface.SetProperty(settings, "GL_" #id "[0]", errstr); \
10443 scriptInterface.SetProperty(settings, "GL_" #id "[1]", errstr); \
10444 } else { \
10445 scriptInterface.SetProperty(settings, "GL_" #id "[0]", f[0]); \
10446 scriptInterface.SetProperty(settings, "GL_" #id "[1]", f[1]); \
10447 } \
10448 } while (false)
10449
10450 #define STRING(id) do { \
10451 const char* c = (const char*)glGetString(GL_##id); \
10452 if (!c) c = ""; \
10453 if (ogl_SquelchError(GL_INVALID_ENUM)) c = errstr; \
10454 scriptInterface.SetProperty(settings, "GL_" #id, std::string(c)); \
10455 } while (false)
10456
10457 #define QUERY(target, pname) do { \
10458 GLint i = -1; \
10459 pglGetQueryivARB(GL_##target, GL_##pname, &i); \
10460 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10461 scriptInterface.SetProperty(settings, "GL_" #target ".GL_" #pname, errstr); \
10462 else \
10463 scriptInterface.SetProperty(settings, "GL_" #target ".GL_" #pname, i); \
10464 } while (false)
10465
10466 #define VERTEXPROGRAM(id) do { \
10467 GLint i = -1; \
10468 pglGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_##id, &i); \
10469 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10470 scriptInterface.SetProperty(settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, errstr); \
10471 else \
10472 scriptInterface.SetProperty(settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, i); \
10473 } while (false)
10474
10475 #define FRAGMENTPROGRAM(id) do { \
10476 GLint i = -1; \
10477 pglGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_##id, &i); \
10478 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10479 scriptInterface.SetProperty(settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, errstr); \
10480 else \
10481 scriptInterface.SetProperty(settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, i); \
10482 } while (false)
10483
10484 #define BOOL(id) INTEGER(id)
10485
10486 ogl_WarnIfError();
10487
10488 // Core OpenGL 1.3:
10489 // (We don't bother checking extension strings for anything older than 1.3;
10490 // it'll just produce harmless warnings)
10491 STRING(VERSION);
10492 STRING(VENDOR);
10493 STRING(RENDERER);
10494 STRING(EXTENSIONS);
10495 #if !CONFIG2_GLES
10496 INTEGER(MAX_LIGHTS);
10497 INTEGER(MAX_CLIP_PLANES);
10498 // Skip MAX_COLOR_MATRIX_STACK_DEPTH (only in imaging subset)
10499 INTEGER(MAX_MODELVIEW_STACK_DEPTH);
10500 INTEGER(MAX_PROJECTION_STACK_DEPTH);
10501 INTEGER(MAX_TEXTURE_STACK_DEPTH);
10502 #endif
10503 INTEGER(SUBPIXEL_BITS);
10504 #if !CONFIG2_GLES
10505 INTEGER(MAX_3D_TEXTURE_SIZE);
10506 #endif
10507 INTEGER(MAX_TEXTURE_SIZE);
10508 INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE);
10509 #if !CONFIG2_GLES
10510 INTEGER(MAX_PIXEL_MAP_TABLE);
10511 INTEGER(MAX_NAME_STACK_DEPTH);
10512 INTEGER(MAX_LIST_NESTING);
10513 INTEGER(MAX_EVAL_ORDER);
10514 #endif
10515 INTEGER2(MAX_VIEWPORT_DIMS);
10516 #if !CONFIG2_GLES
10517 INTEGER(MAX_ATTRIB_STACK_DEPTH);
10518 INTEGER(MAX_CLIENT_ATTRIB_STACK_DEPTH);
10519 INTEGER(AUX_BUFFERS);
10520 BOOL(RGBA_MODE);
10521 BOOL(INDEX_MODE);
10522 BOOL(DOUBLEBUFFER);
10523 BOOL(STEREO);
10524 #endif
10525 FLOAT2(ALIASED_POINT_SIZE_RANGE);
10526 #if !CONFIG2_GLES
10527 FLOAT2(SMOOTH_POINT_SIZE_RANGE);
10528 FLOAT(SMOOTH_POINT_SIZE_GRANULARITY);
10529 #endif
10530 FLOAT2(ALIASED_LINE_WIDTH_RANGE);
10531 #if !CONFIG2_GLES
10532 FLOAT2(SMOOTH_LINE_WIDTH_RANGE);
10533 FLOAT(SMOOTH_LINE_WIDTH_GRANULARITY);
10534 // Skip MAX_CONVOLUTION_WIDTH, MAX_CONVOLUTION_HEIGHT (only in imaging subset)
10535 INTEGER(MAX_ELEMENTS_INDICES);
10536 INTEGER(MAX_ELEMENTS_VERTICES);
10537 INTEGER(MAX_TEXTURE_UNITS);
10538 #endif
10539 INTEGER(SAMPLE_BUFFERS);
10540 INTEGER(SAMPLES);
10541 // TODO: compressed texture formats
10542 INTEGER(RED_BITS);
10543 INTEGER(GREEN_BITS);
10544 INTEGER(BLUE_BITS);
10545 INTEGER(ALPHA_BITS);
10546 #if !CONFIG2_GLES
10547 INTEGER(INDEX_BITS);
10548 #endif
10549 INTEGER(DEPTH_BITS);
10550 INTEGER(STENCIL_BITS);
10551 #if !CONFIG2_GLES
10552 INTEGER(ACCUM_RED_BITS);
10553 INTEGER(ACCUM_GREEN_BITS);
10554 INTEGER(ACCUM_BLUE_BITS);
10555 INTEGER(ACCUM_ALPHA_BITS);
10556 #endif
10557
10558 #if !CONFIG2_GLES
10559
10560 // Core OpenGL 2.0 (treated as extensions):
10561
10562 if (ogl_HaveExtension("GL_EXT_texture_lod_bias"))
10563 {
10564 FLOAT(MAX_TEXTURE_LOD_BIAS_EXT);
10565 }
10566
10567 if (ogl_HaveExtension("GL_ARB_occlusion_query"))
10568 {
10569 QUERY(SAMPLES_PASSED, QUERY_COUNTER_BITS);
10570 }
10571
10572 if (ogl_HaveExtension("GL_ARB_shading_language_100"))
10573 {
10574 STRING(SHADING_LANGUAGE_VERSION_ARB);
10575 }
10576
10577 if (ogl_HaveExtension("GL_ARB_vertex_shader"))
10578 {
10579 INTEGER(MAX_VERTEX_ATTRIBS_ARB);
10580 INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB);
10581 INTEGER(MAX_VARYING_FLOATS_ARB);
10582 INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB);
10583 INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB);
10584 }
10585
10586 if (ogl_HaveExtension("GL_ARB_fragment_shader"))
10587 {
10588 INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB);
10589 }
10590
10591 if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader") ||
10592 ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
10593 {
10594 INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB);
10595 INTEGER(MAX_TEXTURE_COORDS_ARB);
10596 }
10597
10598 if (ogl_HaveExtension("GL_ARB_draw_buffers"))
10599 {
10600 INTEGER(MAX_DRAW_BUFFERS_ARB);
10601 }
10602
10603 // Core OpenGL 3.0:
10604
10605 if (ogl_HaveExtension("GL_EXT_gpu_shader4"))
10606 {
10607 INTEGER(MIN_PROGRAM_TEXEL_OFFSET); // no _EXT version of these in glext.h
10608 INTEGER(MAX_PROGRAM_TEXEL_OFFSET);
10609 }
10610
10611 if (ogl_HaveExtension("GL_EXT_framebuffer_object"))
10612 {
10613 INTEGER(MAX_COLOR_ATTACHMENTS_EXT);
10614 INTEGER(MAX_RENDERBUFFER_SIZE_EXT);
10615 }
10616
10617 if (ogl_HaveExtension("GL_EXT_framebuffer_multisample"))
10618 {
10619 INTEGER(MAX_SAMPLES_EXT);
10620 }
10621
10622 if (ogl_HaveExtension("GL_EXT_texture_array"))
10623 {
10624 INTEGER(MAX_ARRAY_TEXTURE_LAYERS_EXT);
10625 }
10626
10627 if (ogl_HaveExtension("GL_EXT_transform_feedback"))
10628 {
10629 INTEGER(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT);
10630 INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT);
10631 INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT);
10632 }
10633
10634
10635 // Other interesting extensions:
10636
10637 if (ogl_HaveExtension("GL_EXT_timer_query") || ogl_HaveExtension("GL_ARB_timer_query"))
10638 {
10639 QUERY(TIME_ELAPSED, QUERY_COUNTER_BITS);
10640 }
10641
10642 if (ogl_HaveExtension("GL_ARB_timer_query"))
10643 {
10644 QUERY(TIMESTAMP, QUERY_COUNTER_BITS);
10645 }
10646
10647 if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"))
10648 {
10649 FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT);
10650 }
10651
10652 if (ogl_HaveExtension("GL_ARB_texture_rectangle"))
10653 {
10654 INTEGER(MAX_RECTANGLE_TEXTURE_SIZE_ARB);
10655 }
10656
10657 if (ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
10658 {
10659 INTEGER(MAX_PROGRAM_MATRICES_ARB);
10660 INTEGER(MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB);
10661 }
10662
10663 if (ogl_HaveExtension("GL_ARB_vertex_program"))
10664 {
10665 VERTEXPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
10666 VERTEXPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
10667 VERTEXPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
10668 VERTEXPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
10669 VERTEXPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
10670 VERTEXPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
10671 VERTEXPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
10672 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
10673 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
10674 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
10675 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
10676 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
10677
10678 if (ogl_HaveExtension("GL_ARB_fragment_program"))
10679 {
10680 // The spec seems to say these should be supported, but
10681 // Mesa complains about them so let's not bother
10682 /*
10683 VERTEXPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
10684 VERTEXPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
10685 VERTEXPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
10686 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
10687 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
10688 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
10689 */
10690 }
10691 }
10692
10693 if (ogl_HaveExtension("GL_ARB_fragment_program"))
10694 {
10695 FRAGMENTPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
10696 FRAGMENTPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
10697 FRAGMENTPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
10698 FRAGMENTPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
10699 FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
10700 FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
10701 FRAGMENTPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
10702 FRAGMENTPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
10703 FRAGMENTPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
10704 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
10705 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
10706 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
10707 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
10708 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
10709 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
10710 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
10711
10712 if (ogl_HaveExtension("GL_ARB_vertex_program"))
10713 {
10714 // The spec seems to say these should be supported, but
10715 // Intel drivers on Windows complain about them so let's not bother
10716 /*
10717 FRAGMENTPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
10718 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
10719 */
10720 }
10721 }
10722
10723 if (ogl_HaveExtension("GL_ARB_geometry_shader4"))
10724 {
10725 INTEGER(MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB);
10726 INTEGER(MAX_GEOMETRY_OUTPUT_VERTICES_ARB);
10727 INTEGER(MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB);
10728 INTEGER(MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB);
10729 INTEGER(MAX_GEOMETRY_VARYING_COMPONENTS_ARB);
10730 INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB);
10731 }
10732
10733 #else // CONFIG2_GLES
10734
10735 // Core OpenGL ES 2.0:
10736
10737 STRING(SHADING_LANGUAGE_VERSION);
10738 INTEGER(MAX_VERTEX_ATTRIBS);
10739 INTEGER(MAX_VERTEX_UNIFORM_VECTORS);
10740 INTEGER(MAX_VARYING_VECTORS);
10741 INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
10742 INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS);
10743 INTEGER(MAX_FRAGMENT_UNIFORM_VECTORS);
10744 INTEGER(MAX_TEXTURE_IMAGE_UNITS);
10745 INTEGER(MAX_RENDERBUFFER_SIZE);
10746
10747 #endif // CONFIG2_GLES
10748
10749
10750 #ifdef SDL_VIDEO_DRIVER_X11
10751
10752 #define GLXQCR_INTEGER(id) do { \
10753 unsigned int i = UINT_MAX; \
10754 if (pglXQueryCurrentRendererIntegerMESA(id, &i)) \
10755 scriptInterface.SetProperty(settings, #id, i); \
10756 } while (false)
10757
10758 #define GLXQCR_INTEGER2(id) do { \
10759 unsigned int i[2] = { UINT_MAX, UINT_MAX }; \
10760 if (pglXQueryCurrentRendererIntegerMESA(id, i)) { \
10761 scriptInterface.SetProperty(settings, #id "[0]", i[0]); \
10762 scriptInterface.SetProperty(settings, #id "[1]", i[1]); \
10763 } \
10764 } while (false)
10765
10766 #define GLXQCR_INTEGER3(id) do { \
10767 unsigned int i[3] = { UINT_MAX, UINT_MAX, UINT_MAX }; \
10768 if (pglXQueryCurrentRendererIntegerMESA(id, i)) { \
10769 scriptInterface.SetProperty(settings, #id "[0]", i[0]); \
10770 scriptInterface.SetProperty(settings, #id "[1]", i[1]); \
10771 scriptInterface.SetProperty(settings, #id "[2]", i[2]); \
10772 } \
10773 } while (false)
10774
10775 #define GLXQCR_STRING(id) do { \
10776 const char* str = pglXQueryCurrentRendererStringMESA(id); \
10777 if (str) \
10778 scriptInterface.SetProperty(settings, #id ".string", str); \
10779 } while (false)
10780
10781
10782 SDL_SysWMinfo wminfo;
10783 SDL_VERSION(&wminfo.version);
10784 const int ret = SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &wminfo);
10785 if (ret && wminfo.subsystem == SDL_SYSWM_X11)
10786 {
10787 Display* dpy = wminfo.info.x11.display;
10788 int scrnum = DefaultScreen(dpy);
10789
10790 const char* glxexts = glXQueryExtensionsString(dpy, scrnum);
10791
10792 scriptInterface.SetProperty(settings, "glx_extensions", glxexts);
10793
10794 if (strstr(glxexts, "GLX_MESA_query_renderer") && pglXQueryCurrentRendererIntegerMESA && pglXQueryCurrentRendererStringMESA)
10795 {
10796 GLXQCR_INTEGER(GLX_RENDERER_VENDOR_ID_MESA);
10797 GLXQCR_INTEGER(GLX_RENDERER_DEVICE_ID_MESA);
10798 GLXQCR_INTEGER3(GLX_RENDERER_VERSION_MESA);
10799 GLXQCR_INTEGER(GLX_RENDERER_ACCELERATED_MESA);
10800 GLXQCR_INTEGER(GLX_RENDERER_VIDEO_MEMORY_MESA);
10801 GLXQCR_INTEGER(GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA);
10802 GLXQCR_INTEGER(GLX_RENDERER_PREFERRED_PROFILE_MESA);
10803 GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA);
10804 GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA);
10805 GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA);
10806 GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA);
10807 GLXQCR_STRING(GLX_RENDERER_VENDOR_ID_MESA);
10808 GLXQCR_STRING(GLX_RENDERER_DEVICE_ID_MESA);
10809 }
10810 }
10811 #endif // SDL_VIDEO_DRIVER_X11
10812
10813 }
10814Index: source/ps/Mod.cpp
10815===================================================================
10816--- source/ps/Mod.cpp (revision 23275)
10817+++ source/ps/Mod.cpp (working copy)
10818@@ -1,158 +1,159 @@
10819 /* Copyright (C) 2019 Wildfire Games.
10820 * This file is part of 0 A.D.
10821 *
10822 * 0 A.D. is free software: you can redistribute it and/or modify
10823 * it under the terms of the GNU General Public License as published by
10824 * the Free Software Foundation, either version 2 of the License, or
10825 * (at your option) any later version.
10826 *
10827 * 0 A.D. is distributed in the hope that it will be useful,
10828 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10829 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10830 * GNU General Public License for more details.
10831 *
10832 * You should have received a copy of the GNU General Public License
10833 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
10834 */
10835
10836 #include "precompiled.h"
10837
10838 #include "ps/Mod.h"
10839
10840 #include <algorithm>
10841
10842 #include "lib/file/file_system.h"
10843 #include "lib/file/vfs/vfs.h"
10844 #include "lib/utf8.h"
10845 #include "ps/Filesystem.h"
10846 #include "ps/GameSetup/GameSetup.h"
10847 #include "ps/GameSetup/Paths.h"
10848+#include "ps/Pyrogenesis.h"
10849 #include "scriptinterface/ScriptInterface.h"
10850 #include "scriptinterface/ScriptRuntime.h"
10851
10852 std::vector<CStr> g_modsLoaded;
10853
10854 std::vector<std::vector<CStr>> g_LoadedModVersions;
10855
10856 CmdLineArgs g_args;
10857
10858 JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface)
10859 {
10860 JSContext* cx = scriptInterface.GetContext();
10861 JSAutoRequest rq(cx);
10862 JS::RootedObject obj(cx, JS_NewPlainObject(cx));
10863
10864 const Paths paths(g_args);
10865
10866 // loop over all possible paths
10867 OsPath modPath = paths.RData()/"mods";
10868 OsPath modUserPath = paths.UserData()/"mods";
10869
10870 DirectoryNames modDirs;
10871 DirectoryNames modDirsUser;
10872
10873 GetDirectoryEntries(modPath, NULL, &modDirs);
10874 // Sort modDirs so that we can do a fast lookup below
10875 std::sort(modDirs.begin(), modDirs.end());
10876
10877 PIVFS vfs = CreateVfs();
10878
10879 for (DirectoryNames::iterator iter = modDirs.begin(); iter != modDirs.end(); ++iter)
10880 {
10881 vfs->Clear();
10882 if (vfs->Mount(L"", modPath / *iter, VFS_MOUNT_MUST_EXIST) < 0)
10883 continue;
10884
10885 CVFSFile modinfo;
10886 if (modinfo.Load(vfs, L"mod.json", false) != PSRETURN_OK)
10887 continue;
10888
10889 JS::RootedValue json(cx);
10890 if (!scriptInterface.ParseJSON(modinfo.GetAsString(), &json))
10891 continue;
10892
10893 // Valid mod, add it to our structure
10894 JS_SetProperty(cx, obj, utf8_from_wstring(iter->string()).c_str(), json);
10895 }
10896
10897 GetDirectoryEntries(modUserPath, NULL, &modDirsUser);
10898 bool dev = InDevelopmentCopy();
10899
10900 for (DirectoryNames::iterator iter = modDirsUser.begin(); iter != modDirsUser.end(); ++iter)
10901 {
10902 // If we are in a dev copy we do not mount mods in the user mod folder that
10903 // are already present in the mod folder, thus we skip those here.
10904 if (dev && std::binary_search(modDirs.begin(), modDirs.end(), *iter))
10905 continue;
10906
10907 vfs->Clear();
10908 if (vfs->Mount(L"", modUserPath / *iter, VFS_MOUNT_MUST_EXIST) < 0)
10909 continue;
10910
10911 CVFSFile modinfo;
10912 if (modinfo.Load(vfs, L"mod.json", false) != PSRETURN_OK)
10913 continue;
10914
10915 JS::RootedValue json(cx);
10916 if (!scriptInterface.ParseJSON(modinfo.GetAsString(), &json))
10917 continue;
10918
10919 // Valid mod, add it to our structure
10920 JS_SetProperty(cx, obj, utf8_from_wstring(iter->string()).c_str(), json);
10921 }
10922
10923 return JS::ObjectValue(*obj);
10924 }
10925
10926 void Mod::CacheEnabledModVersions(const shared_ptr<ScriptRuntime>& scriptRuntime)
10927 {
10928 ScriptInterface scriptInterface("Engine", "CacheEnabledModVersions", scriptRuntime);
10929 JSContext* cx = scriptInterface.GetContext();
10930 JSAutoRequest rq(cx);
10931
10932 JS::RootedValue availableMods(cx, GetAvailableMods(scriptInterface));
10933
10934 g_LoadedModVersions.clear();
10935
10936 for (const CStr& mod : g_modsLoaded)
10937 {
10938 // Ignore user and mod mod as they are irrelevant for compatibility checks
10939 if (mod == "mod" || mod == "user")
10940 continue;
10941
10942 CStr version;
10943 JS::RootedValue modData(cx);
10944 if (scriptInterface.GetProperty(availableMods, mod.c_str(), &modData))
10945 scriptInterface.GetProperty(modData, "version", version);
10946
10947 g_LoadedModVersions.push_back({mod, version});
10948 }
10949 }
10950
10951 JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface)
10952 {
10953 JSContext* cx = scriptInterface.GetContext();
10954 JSAutoRequest rq(cx);
10955 JS::RootedValue returnValue(cx);
10956 scriptInterface.ToJSVal(cx, &returnValue, g_LoadedModVersions);
10957 return returnValue;
10958 }
10959
10960 JS::Value Mod::GetEngineInfo(const ScriptInterface& scriptInterface)
10961 {
10962 JSContext* cx = scriptInterface.GetContext();
10963 JSAutoRequest rq(cx);
10964
10965 JS::RootedValue mods(cx, Mod::GetLoadedModsWithVersions(scriptInterface));
10966 JS::RootedValue metainfo(cx);
10967
10968 ScriptInterface::CreateObject(
10969 cx,
10970 &metainfo,
10971 "engine_version", engine_version,
10972 "mods", mods);
10973
10974 scriptInterface.FreezeObject(metainfo, true);
10975
10976 return metainfo;
10977 }
10978Index: source/ps/ModIo.h
10979===================================================================
10980--- source/ps/ModIo.h (revision 23275)
10981+++ source/ps/ModIo.h (working copy)
10982@@ -1,208 +1,209 @@
10983 /* Copyright (C) 2018 Wildfire Games.
10984 *
10985 * Permission is hereby granted, free of charge, to any person obtaining
10986 * a copy of this software and associated documentation files (the
10987 * "Software"), to deal in the Software without restriction, including
10988 * without limitation the rights to use, copy, modify, merge, publish,
10989 * distribute, sublicense, and/or sell copies of the Software, and to
10990 * permit persons to whom the Software is furnished to do so, subject to
10991 * the following conditions:
10992 *
10993 * The above copyright notice and this permission notice shall be included
10994 * in all copies or substantial portions of the Software.
10995 *
10996 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
10997 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
10998 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
10999 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
11000 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
11001 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
11002 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11003 */
11004
11005 #ifndef INCLUDED_MODIO
11006 #define INCLUDED_MODIO
11007
11008 #include "lib/external_libraries/curl.h"
11009+#include "lib/os_path.h"
11010 #include "scriptinterface/ScriptInterface.h"
11011
11012 #include <sodium.h>
11013 #include <string>
11014
11015 // TODO: Allocate instance of the below two using sodium_malloc?
11016 struct PKStruct
11017 {
11018 unsigned char sig_alg[2] = {}; // == "Ed"
11019 unsigned char keynum[8] = {}; // should match the keynum in the sigstruct, else this is the wrong key
11020 unsigned char pk[crypto_sign_PUBLICKEYBYTES] = {};
11021 };
11022
11023 struct SigStruct
11024 {
11025 unsigned char sig_alg[2] = {}; // "ED" (since we only support the hashed mode)
11026 unsigned char keynum[8] = {}; // should match the keynum in the PKStruct
11027 unsigned char sig[crypto_sign_BYTES] = {};
11028 };
11029
11030 struct ModIoModData
11031 {
11032 std::map<std::string, std::string> properties;
11033 std::vector<std::string> dependencies;
11034 SigStruct sig;
11035 };
11036
11037 enum class DownloadProgressStatus {
11038 NONE, // Default state
11039 GAMEID, // The game ID is being downloaded
11040 READY, // The game ID has been downloaded
11041 LISTING, // The mod list is being downloaded
11042 LISTED, // The mod list has been downloaded
11043 DOWNLOADING, // A mod file is being downloaded
11044 SUCCESS, // A mod file has been downloaded
11045
11046 FAILED_GAMEID, // Game ID couldn't be retrieved
11047 FAILED_LISTING, // Mod list couldn't be retrieved
11048 FAILED_DOWNLOADING, // File couldn't be retrieved
11049 FAILED_FILECHECK // The file is corrupted
11050 };
11051
11052 struct DownloadProgressData
11053 {
11054 DownloadProgressStatus status;
11055 double progress;
11056 std::string error;
11057 };
11058
11059 struct DownloadCallbackData;
11060
11061 /**
11062 * mod.io API interfacing code.
11063 *
11064 * Overview
11065 *
11066 * This class interfaces with a remote API provider that returns a list of mod files.
11067 * These can then be downloaded after some cursory checking of well-formedness of the returned
11068 * metadata.
11069 * Downloaded files are checked for well formedness by validating that they fit the size and hash
11070 * indicated by the API, then we check if the file is actually signed by a trusted key, and only
11071 * if all of that is success the file is actually possible to be loaded as a mod.
11072 *
11073 * Security considerations
11074 *
11075 * This both distrusts the loaded JS mods, and the API as much as possible.
11076 * We do not want a malicious mod to use this to download arbitrary files, nor do we want the API
11077 * to make us download something we have not verified.
11078 * Therefore we only allow mods to download one of the mods returned by this class (using indices).
11079 *
11080 * This (mostly) necessitates parsing the API responses here, as opposed to in JS.
11081 * One could alternatively parse the responses in a locked down JS context, but that would require
11082 * storing that code in here, or making sure nobody can overwrite it. Also this would possibly make
11083 * some of the needed accesses for downloading and verifying files a bit more complicated.
11084 *
11085 * Everything downloaded from the API has its signature verified against our public key.
11086 * This is a requirement, as otherwise a compromise of the API would result in users installing
11087 * possibly malicious files.
11088 * So a compromised API can just serve old files that we signed, so in that case there would need
11089 * to be an issue in that old file that was missed.
11090 *
11091 * To limit the extend to how old those files could be the signing key should be rotated
11092 * regularly (e.g. every release). To allow old versions of the engine to still use the API
11093 * files can be signed by both the old and the new key for some amount of time, that however
11094 * only makes sense in case a mod is compatible with both engine versions.
11095 *
11096 * Note that this does not prevent all possible attacks a package manager/update system should
11097 * defend against. This is intentionally not an update system since proper package managers already
11098 * exist. However there is some possible overlap in attack vectors and these should be evalutated
11099 * whether they apply and to what extend we can fix that on our side (or how to get the API provider
11100 * to help us do so). For a list of some possible issues see:
11101 * https://github.com/theupdateframework/specification/blob/master/tuf-spec.md
11102 *
11103 * The mod.io settings are also locked down such that only mods that have been authorized by us
11104 * show up in API queries. This is both done so that all required information (dependencies)
11105 * are stored for the files, and that only mods that have been checked for being ok are actually
11106 * shown to users.
11107 */
11108 class ModIo
11109 {
11110 NONCOPYABLE(ModIo);
11111 public:
11112 ModIo();
11113 ~ModIo();
11114
11115 // Async requests
11116 void StartGetGameId();
11117 void StartListMods();
11118 void StartDownloadMod(size_t idx);
11119
11120 /**
11121 * Advance the current async request and perform final steps if the download is complete.
11122 *
11123 * @param scriptInterface used for parsing the data and possibly install the mod.
11124 * @return true if the download is complete (successful or not), false otherwise.
11125 */
11126 bool AdvanceRequest(const ScriptInterface& scriptInterface);
11127
11128 /**
11129 * Cancel the current async request and clean things up
11130 */
11131 void CancelRequest();
11132
11133 const std::vector<ModIoModData>& GetMods() const
11134 {
11135 return m_ModData;
11136 }
11137 const DownloadProgressData& GetDownloadProgress() const
11138 {
11139 return m_DownloadProgressData;
11140 }
11141
11142 private:
11143 static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp);
11144 static size_t DownloadCallback(void* buffer, size_t size, size_t nmemb, void* userp);
11145 static int DownloadProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
11146
11147 CURLMcode SetupRequest(const std::string& url, bool fileDownload);
11148 void TearDownRequest();
11149
11150 bool ParseGameId(const ScriptInterface& scriptInterface, std::string& err);
11151 bool ParseMods(const ScriptInterface& scriptInterface, std::string& err);
11152
11153 void DeleteDownloadedFile();
11154 bool VerifyDownloadedFile(std::string& err);
11155
11156 // Utility methods for parsing mod.io responses and metadata
11157 static bool ParseGameIdResponse(const ScriptInterface& scriptInterface, const std::string& responseData, int& id, std::string& err);
11158 static bool ParseModsResponse(const ScriptInterface& scriptInterface, const std::string& responseData, std::vector<ModIoModData>& modData, const PKStruct& pk, std::string& err);
11159 static bool ParseSignature(const std::vector<std::string>& minisigs, SigStruct& sig, const PKStruct& pk, std::string& err);
11160
11161 // Url parts
11162 std::string m_BaseUrl;
11163 std::string m_GamesRequest;
11164 std::string m_GameId;
11165
11166 // Query parameters
11167 std::string m_ApiKey;
11168 std::string m_IdQuery;
11169
11170 CURL* m_Curl;
11171 CURLM* m_CurlMulti;
11172 curl_slist* m_Headers;
11173 char m_ErrorBuffer[CURL_ERROR_SIZE];
11174 std::string m_ResponseData;
11175
11176 // Current mod download
11177 int m_DownloadModID;
11178 OsPath m_DownloadFilePath;
11179 DownloadCallbackData* m_CallbackData;
11180 DownloadProgressData m_DownloadProgressData;
11181
11182 PKStruct m_pk;
11183
11184 std::vector<ModIoModData> m_ModData;
11185
11186 friend class TestModIo;
11187 };
11188
11189 extern ModIo* g_ModIo;
11190
11191 #endif // INCLUDED_MODIO
11192Index: source/ps/ProfileViewer.cpp
11193===================================================================
11194--- source/ps/ProfileViewer.cpp (revision 23275)
11195+++ source/ps/ProfileViewer.cpp (working copy)
11196@@ -1,624 +1,625 @@
11197 /* Copyright (C) 2019 Wildfire Games.
11198 * This file is part of 0 A.D.
11199 *
11200 * 0 A.D. is free software: you can redistribute it and/or modify
11201 * it under the terms of the GNU General Public License as published by
11202 * the Free Software Foundation, either version 2 of the License, or
11203 * (at your option) any later version.
11204 *
11205 * 0 A.D. is distributed in the hope that it will be useful,
11206 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11207 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11208 * GNU General Public License for more details.
11209 *
11210 * You should have received a copy of the GNU General Public License
11211 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
11212 */
11213
11214 /*
11215 * Implementation of profile display (containing only display routines,
11216 * the data model(s) are implemented elsewhere).
11217 */
11218
11219 #include "precompiled.h"
11220
11221 #include "ProfileViewer.h"
11222
11223 #include "graphics/FontMetrics.h"
11224 #include "graphics/ShaderManager.h"
11225 #include "graphics/TextRenderer.h"
11226 #include "gui/GUIMatrix.h"
11227 #include "lib/external_libraries/libsdl.h"
11228 #include "ps/CLogger.h"
11229 #include "ps/Filesystem.h"
11230 #include "ps/Hotkey.h"
11231 #include "ps/Profile.h"
11232+#include "ps/Pyrogenesis.h"
11233 #include "renderer/Renderer.h"
11234 #include "scriptinterface/ScriptInterface.h"
11235
11236 #include <algorithm>
11237 #include <ctime>
11238
11239 extern int g_xres, g_yres;
11240
11241 struct CProfileViewerInternals
11242 {
11243 NONCOPYABLE(CProfileViewerInternals); // because of the ofstream
11244 public:
11245 CProfileViewerInternals() {}
11246
11247 /// Whether the profiling display is currently visible
11248 bool profileVisible;
11249
11250 /// List of root tables
11251 std::vector<AbstractProfileTable*> rootTables;
11252
11253 /// Path from a root table (path[0]) to the currently visible table (path[size-1])
11254 std::vector<AbstractProfileTable*> path;
11255
11256 /// Helper functions
11257 void TableIsDeleted(AbstractProfileTable* table);
11258 void NavigateTree(int id);
11259
11260 /// File for saved profile output (reset when the game is restarted)
11261 std::ofstream outputStream;
11262 };
11263
11264
11265 ///////////////////////////////////////////////////////////////////////////////////////////////
11266 // AbstractProfileTable implementation
11267
11268 AbstractProfileTable::~AbstractProfileTable()
11269 {
11270 if (CProfileViewer::IsInitialised())
11271 {
11272 g_ProfileViewer.m->TableIsDeleted(this);
11273 }
11274 }
11275
11276
11277 ///////////////////////////////////////////////////////////////////////////////////////////////
11278 // CProfileViewer implementation
11279
11280
11281 // AbstractProfileTable got deleted, make sure we have no dangling pointers
11282 void CProfileViewerInternals::TableIsDeleted(AbstractProfileTable* table)
11283 {
11284 for(int idx = (int)rootTables.size()-1; idx >= 0; --idx)
11285 {
11286 if (rootTables[idx] == table)
11287 rootTables.erase(rootTables.begin() + idx);
11288 }
11289
11290 for(size_t idx = 0; idx < path.size(); ++idx)
11291 {
11292 if (path[idx] != table)
11293 continue;
11294
11295 path.erase(path.begin() + idx, path.end());
11296 if (path.size() == 0)
11297 profileVisible = false;
11298 }
11299 }
11300
11301
11302 // Move into child tables or return to parent tables based on the given number
11303 void CProfileViewerInternals::NavigateTree(int id)
11304 {
11305 if (id == 0)
11306 {
11307 if (path.size() > 1)
11308 path.pop_back();
11309 }
11310 else
11311 {
11312 AbstractProfileTable* table = path[path.size() - 1];
11313 size_t numrows = table->GetNumberRows();
11314
11315 for(size_t row = 0; row < numrows; ++row)
11316 {
11317 AbstractProfileTable* child = table->GetChild(row);
11318
11319 if (!child)
11320 continue;
11321
11322 --id;
11323 if (id == 0)
11324 {
11325 path.push_back(child);
11326 break;
11327 }
11328 }
11329 }
11330 }
11331
11332
11333 // Construction/Destruction
11334 CProfileViewer::CProfileViewer()
11335 {
11336 m = new CProfileViewerInternals;
11337 m->profileVisible = false;
11338 }
11339
11340 CProfileViewer::~CProfileViewer()
11341 {
11342 delete m;
11343 }
11344
11345
11346 // Render
11347 void CProfileViewer::RenderProfile()
11348 {
11349 if (!m->profileVisible)
11350 return;
11351
11352 if (!m->path.size())
11353 {
11354 m->profileVisible = false;
11355 return;
11356 }
11357
11358 PROFILE3_GPU("profile viewer");
11359
11360 glEnable(GL_BLEND);
11361 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
11362
11363 AbstractProfileTable* table = m->path[m->path.size() - 1];
11364 const std::vector<ProfileColumn>& columns = table->GetColumns();
11365 size_t numrows = table->GetNumberRows();
11366
11367 CStrIntern font_name("mono-stroke-10");
11368 CFontMetrics font(font_name);
11369 int lineSpacing = font.GetLineSpacing();
11370
11371 // Render background
11372 GLint estimate_height;
11373 GLint estimate_width;
11374
11375 estimate_width = 50;
11376 for(size_t i = 0; i < columns.size(); ++i)
11377 estimate_width += (GLint)columns[i].width;
11378
11379 estimate_height = 3 + (GLint)numrows;
11380 if (m->path.size() > 1)
11381 estimate_height += 2;
11382 estimate_height = lineSpacing*estimate_height;
11383
11384 CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
11385 solidTech->BeginPass();
11386 CShaderProgramPtr solidShader = solidTech->GetShader();
11387
11388 solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.5f);
11389
11390 CMatrix3D transform = GetDefaultGuiMatrix();
11391 solidShader->Uniform(str_transform, transform);
11392
11393 float backgroundVerts[] = {
11394 (float)estimate_width, 0.0f,
11395 0.0f, 0.0f,
11396 0.0f, (float)estimate_height,
11397 0.0f, (float)estimate_height,
11398 (float)estimate_width, (float)estimate_height,
11399 (float)estimate_width, 0.0f
11400 };
11401 solidShader->VertexPointer(2, GL_FLOAT, 0, backgroundVerts);
11402 solidShader->AssertPointersBound();
11403 glDrawArrays(GL_TRIANGLES, 0, 6);
11404
11405 transform.PostTranslate(22.0f, lineSpacing*3.0f, 0.0f);
11406 solidShader->Uniform(str_transform, transform);
11407
11408 // Draw row backgrounds
11409 for (size_t row = 0; row < numrows; ++row)
11410 {
11411 if (row % 2)
11412 solidShader->Uniform(str_color, 1.0f, 1.0f, 1.0f, 0.1f);
11413 else
11414 solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.1f);
11415
11416 float rowVerts[] = {
11417 -22.f, 2.f,
11418 estimate_width-22.f, 2.f,
11419 estimate_width-22.f, 2.f-lineSpacing,
11420
11421 estimate_width-22.f, 2.f-lineSpacing,
11422 -22.f, 2.f-lineSpacing,
11423 -22.f, 2.f
11424 };
11425 solidShader->VertexPointer(2, GL_FLOAT, 0, rowVerts);
11426 solidShader->AssertPointersBound();
11427 glDrawArrays(GL_TRIANGLES, 0, 6);
11428
11429 transform.PostTranslate(0.0f, lineSpacing, 0.0f);
11430 solidShader->Uniform(str_transform, transform);
11431 }
11432
11433 solidTech->EndPass();
11434
11435 // Print table and column titles
11436
11437 CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
11438 textTech->BeginPass();
11439
11440 CTextRenderer textRenderer(textTech->GetShader());
11441 textRenderer.Font(font_name);
11442 textRenderer.Color(1.0f, 1.0f, 1.0f);
11443
11444 textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str());
11445
11446 textRenderer.Translate(22.0f, lineSpacing*2.0f, 0.0f);
11447
11448 float colX = 0.0f;
11449 for (size_t col = 0; col < columns.size(); ++col)
11450 {
11451 CStrW text = columns[col].title.FromUTF8();
11452 int w, h;
11453 font.CalculateStringSize(text.c_str(), w, h);
11454
11455 float x = colX;
11456 if (col > 0) // right-align all but the first column
11457 x += columns[col].width - w;
11458 textRenderer.Put(x, 0.0f, text.c_str());
11459
11460 colX += columns[col].width;
11461 }
11462
11463 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
11464
11465 // Print rows
11466 int currentExpandId = 1;
11467
11468 for (size_t row = 0; row < numrows; ++row)
11469 {
11470 if (table->IsHighlightRow(row))
11471 textRenderer.Color(1.0f, 0.5f, 0.5f);
11472 else
11473 textRenderer.Color(1.0f, 1.0f, 1.0f);
11474
11475 if (table->GetChild(row))
11476 {
11477 textRenderer.PrintfAt(-15.0f, 0.0f, L"%d", currentExpandId);
11478 currentExpandId++;
11479 }
11480
11481 float colX = 0.0f;
11482 for (size_t col = 0; col < columns.size(); ++col)
11483 {
11484 CStrW text = table->GetCellText(row, col).FromUTF8();
11485 int w, h;
11486 font.CalculateStringSize(text.c_str(), w, h);
11487
11488 float x = colX;
11489 if (col > 0) // right-align all but the first column
11490 x += columns[col].width - w;
11491 textRenderer.Put(x, 0.0f, text.c_str());
11492
11493 colX += columns[col].width;
11494 }
11495
11496 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
11497 }
11498
11499 textRenderer.Color(1.0f, 1.0f, 1.0f);
11500
11501 if (m->path.size() > 1)
11502 {
11503 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
11504 textRenderer.Put(-15.0f, 0.0f, L"0");
11505 textRenderer.Put(0.0f, 0.0f, L"back to parent");
11506 }
11507
11508 textRenderer.Render();
11509 textTech->EndPass();
11510
11511 glDisable(GL_BLEND);
11512
11513 glEnable(GL_DEPTH_TEST);
11514 }
11515
11516
11517 // Handle input
11518 InReaction CProfileViewer::Input(const SDL_Event_* ev)
11519 {
11520 switch(ev->ev.type)
11521 {
11522 case SDL_KEYDOWN:
11523 {
11524 if (!m->profileVisible)
11525 break;
11526
11527 int k = ev->ev.key.keysym.sym;
11528 if (k >= SDLK_0 && k <= SDLK_9)
11529 {
11530 m->NavigateTree(k - SDLK_0);
11531 return IN_HANDLED;
11532 }
11533 break;
11534 }
11535 case SDL_HOTKEYDOWN:
11536 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
11537
11538 if( hotkey == "profile.toggle" )
11539 {
11540 if (!m->profileVisible)
11541 {
11542 if (m->rootTables.size())
11543 {
11544 m->profileVisible = true;
11545 m->path.push_back(m->rootTables[0]);
11546 }
11547 }
11548 else
11549 {
11550 size_t i;
11551
11552 for(i = 0; i < m->rootTables.size(); ++i)
11553 {
11554 if (m->rootTables[i] == m->path[0])
11555 break;
11556 }
11557 i++;
11558
11559 m->path.clear();
11560 if (i < m->rootTables.size())
11561 {
11562 m->path.push_back(m->rootTables[i]);
11563 }
11564 else
11565 {
11566 m->profileVisible = false;
11567 }
11568 }
11569 return( IN_HANDLED );
11570 }
11571 else if( hotkey == "profile.save" )
11572 {
11573 SaveToFile();
11574 return( IN_HANDLED );
11575 }
11576 break;
11577 }
11578 return( IN_PASS );
11579 }
11580
11581 InReaction CProfileViewer::InputThunk(const SDL_Event_* ev)
11582 {
11583 if (CProfileViewer::IsInitialised())
11584 return g_ProfileViewer.Input(ev);
11585
11586 return IN_PASS;
11587 }
11588
11589
11590 // Add a table to the list of roots
11591 void CProfileViewer::AddRootTable(AbstractProfileTable* table, bool front)
11592 {
11593 if (front)
11594 m->rootTables.insert(m->rootTables.begin(), table);
11595 else
11596 m->rootTables.push_back(table);
11597 }
11598
11599 namespace
11600 {
11601 struct WriteTable
11602 {
11603 std::ofstream& f;
11604 WriteTable(std::ofstream& f) : f(f) {}
11605
11606 void operator() (AbstractProfileTable* table)
11607 {
11608 std::vector<CStr> data; // 2d array of (rows+head)*columns elements
11609
11610 const std::vector<ProfileColumn>& columns = table->GetColumns();
11611
11612 // Add column headers to 'data'
11613 for (std::vector<ProfileColumn>::const_iterator col_it = columns.begin();
11614 col_it != columns.end(); ++col_it)
11615 data.push_back(col_it->title);
11616
11617 // Recursively add all profile data to 'data'
11618 WriteRows(1, table, data);
11619
11620 // Calculate the width of each column ( = the maximum width of
11621 // any value in that column)
11622 std::vector<size_t> columnWidths;
11623 size_t cols = columns.size();
11624 for (size_t c = 0; c < cols; ++c)
11625 {
11626 size_t max = 0;
11627 for (size_t i = c; i < data.size(); i += cols)
11628 max = std::max(max, data[i].length());
11629 columnWidths.push_back(max);
11630 }
11631
11632 // Output data as a formatted table:
11633
11634 f << "\n\n" << table->GetTitle() << "\n";
11635
11636 if (cols == 0) // avoid divide-by-zero
11637 return;
11638
11639 for (size_t r = 0; r < data.size()/cols; ++r)
11640 {
11641 for (size_t c = 0; c < cols; ++c)
11642 f << (c ? " | " : "\n")
11643 << data[r*cols + c].Pad(PS_TRIM_RIGHT, columnWidths[c]);
11644
11645 // Add dividers under some rows. (Currently only the first, since
11646 // that contains the column headers.)
11647 if (r == 0)
11648 for (size_t c = 0; c < cols; ++c)
11649 f << (c ? "-|-" : "\n")
11650 << CStr::Repeat("-", columnWidths[c]);
11651 }
11652 }
11653
11654 void WriteRows(int indent, AbstractProfileTable* table, std::vector<CStr>& data)
11655 {
11656 const std::vector<ProfileColumn>& columns = table->GetColumns();
11657
11658 for (size_t r = 0; r < table->GetNumberRows(); ++r)
11659 {
11660 // Do pretty tree-structure indenting
11661 CStr indentation = CStr::Repeat("| ", indent-1);
11662 if (r+1 == table->GetNumberRows())
11663 indentation += "'-";
11664 else
11665 indentation += "|-";
11666
11667 for (size_t c = 0; c < columns.size(); ++c)
11668 if (c == 0)
11669 data.push_back(indentation + table->GetCellText(r, c));
11670 else
11671 data.push_back(table->GetCellText(r, c));
11672
11673 if (table->GetChild(r))
11674 WriteRows(indent+1, table->GetChild(r), data);
11675 }
11676 }
11677
11678 private:
11679 const WriteTable& operator=(const WriteTable&);
11680 };
11681
11682 struct DumpTable
11683 {
11684 const ScriptInterface& m_ScriptInterface;
11685 JS::PersistentRooted<JS::Value> m_Root;
11686 DumpTable(const ScriptInterface& scriptInterface, JS::HandleValue root) :
11687 m_ScriptInterface(scriptInterface), m_Root(scriptInterface.GetJSRuntime(), root)
11688 {
11689 }
11690
11691 // std::for_each requires a move constructor and the use of JS::PersistentRooted<T> apparently breaks a requirement for an
11692 // automatic move constructor
11693 DumpTable(DumpTable && original) :
11694 m_ScriptInterface(original.m_ScriptInterface),
11695 m_Root(original.m_ScriptInterface.GetJSRuntime(), original.m_Root.get())
11696 {
11697 }
11698
11699 void operator() (AbstractProfileTable* table)
11700 {
11701 JSContext* cx = m_ScriptInterface.GetContext();
11702 JSAutoRequest rq(cx);
11703
11704 JS::RootedValue t(cx);
11705 ScriptInterface::CreateObject(
11706 cx,
11707 &t,
11708 "cols", DumpCols(table),
11709 "data", DumpRows(table));
11710
11711 m_ScriptInterface.SetProperty(m_Root, table->GetTitle().c_str(), t);
11712 }
11713
11714 std::vector<std::string> DumpCols(AbstractProfileTable* table)
11715 {
11716 std::vector<std::string> titles;
11717
11718 const std::vector<ProfileColumn>& columns = table->GetColumns();
11719
11720 for (size_t c = 0; c < columns.size(); ++c)
11721 titles.push_back(columns[c].title);
11722
11723 return titles;
11724 }
11725
11726 JS::Value DumpRows(AbstractProfileTable* table)
11727 {
11728 JSContext* cx = m_ScriptInterface.GetContext();
11729 JSAutoRequest rq(cx);
11730
11731 JS::RootedValue data(cx);
11732 ScriptInterface::CreateObject(cx, &data);
11733
11734 const std::vector<ProfileColumn>& columns = table->GetColumns();
11735
11736 for (size_t r = 0; r < table->GetNumberRows(); ++r)
11737 {
11738 JS::RootedValue row(cx);
11739 ScriptInterface::CreateArray(cx, &row);
11740
11741 m_ScriptInterface.SetProperty(data, table->GetCellText(r, 0).c_str(), row);
11742
11743 if (table->GetChild(r))
11744 {
11745 JS::RootedValue childRows(cx, DumpRows(table->GetChild(r)));
11746 m_ScriptInterface.SetPropertyInt(row, 0, childRows);
11747 }
11748
11749 for (size_t c = 1; c < columns.size(); ++c)
11750 m_ScriptInterface.SetPropertyInt(row, c, table->GetCellText(r, c));
11751 }
11752
11753 return data;
11754 }
11755
11756 private:
11757 const DumpTable& operator=(const DumpTable&);
11758 };
11759
11760 bool SortByName(AbstractProfileTable* a, AbstractProfileTable* b)
11761 {
11762 return (a->GetName() < b->GetName());
11763 }
11764 }
11765
11766 void CProfileViewer::SaveToFile()
11767 {
11768 // Open the file, if necessary. If this method is called several times,
11769 // the profile results will be appended to the previous ones from the same
11770 // run.
11771 if (! m->outputStream.is_open())
11772 {
11773 // Open the file. (It will be closed when the CProfileViewer
11774 // destructor is called.)
11775 OsPath path = psLogDir()/"profile.txt";
11776 m->outputStream.open(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
11777
11778 if (m->outputStream.fail())
11779 {
11780 LOGERROR("Failed to open profile log file");
11781 return;
11782 }
11783 else
11784 {
11785 LOGMESSAGERENDER("Profiler snapshot saved to '%s'", path.string8());
11786 }
11787 }
11788
11789 time_t t;
11790 time(&t);
11791 m->outputStream << "================================================================\n\n";
11792 m->outputStream << "PS profiler snapshot - " << asctime(localtime(&t));
11793
11794 std::vector<AbstractProfileTable*> tables = m->rootTables;
11795 sort(tables.begin(), tables.end(), SortByName);
11796 for_each(tables.begin(), tables.end(), WriteTable(m->outputStream));
11797
11798 m->outputStream << "\n\n================================================================\n";
11799 m->outputStream.flush();
11800 }
11801
11802 void CProfileViewer::ShowTable(const CStr& table)
11803 {
11804 m->path.clear();
11805
11806 if (table.length() > 0)
11807 {
11808 for (size_t i = 0; i < m->rootTables.size(); ++i)
11809 {
11810 if (m->rootTables[i]->GetName() == table)
11811 {
11812 m->path.push_back(m->rootTables[i]);
11813 m->profileVisible = true;
11814 return;
11815 }
11816 }
11817 }
11818
11819 // No matching table found, so don't display anything
11820 m->profileVisible = false;
11821 }
11822Index: source/ps/Profiler2.cpp
11823===================================================================
11824--- source/ps/Profiler2.cpp (revision 23275)
11825+++ source/ps/Profiler2.cpp (working copy)
11826@@ -1,981 +1,983 @@
11827 /* Copyright (C) 2019 Wildfire Games.
11828 *
11829 * Permission is hereby granted, free of charge, to any person obtaining
11830 * a copy of this software and associated documentation files (the
11831 * "Software"), to deal in the Software without restriction, including
11832 * without limitation the rights to use, copy, modify, merge, publish,
11833 * distribute, sublicense, and/or sell copies of the Software, and to
11834 * permit persons to whom the Software is furnished to do so, subject to
11835 * the following conditions:
11836 *
11837 * The above copyright notice and this permission notice shall be included
11838 * in all copies or substantial portions of the Software.
11839 *
11840 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
11841 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
11842 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11843 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
11844 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
11845 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
11846 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11847 */
11848
11849 #include "precompiled.h"
11850
11851 #include "Profiler2.h"
11852
11853 #include "lib/allocators/shared_ptr.h"
11854+#include "lib/os_path.h"
11855 #include "ps/CLogger.h"
11856 #include "ps/CStr.h"
11857 #include "ps/Profiler2GPU.h"
11858+#include "ps/Pyrogenesis.h"
11859 #include "third_party/mongoose/mongoose.h"
11860
11861 #include <iomanip>
11862 #include <map>
11863 #include <unordered_map>
11864
11865 CProfiler2 g_Profiler2;
11866
11867 const size_t CProfiler2::MAX_ATTRIBUTE_LENGTH = 256;
11868
11869 // TODO: what's a good size?
11870 const size_t CProfiler2::BUFFER_SIZE = 4 * 1024 * 1024;
11871 const size_t CProfiler2::HOLD_BUFFER_SIZE = 128 * 1024;
11872
11873 // A human-recognisable pattern (for debugging) followed by random bytes (for uniqueness)
11874 const u8 CProfiler2::RESYNC_MAGIC[8] = {0x11, 0x22, 0x33, 0x44, 0xf4, 0x93, 0xbe, 0x15};
11875
11876 thread_local CProfiler2::ThreadStorage* CProfiler2::m_CurrentStorage = nullptr;
11877
11878 CProfiler2::CProfiler2() :
11879 m_Initialised(false), m_FrameNumber(0), m_MgContext(NULL), m_GPU(NULL)
11880 {
11881 }
11882
11883 CProfiler2::~CProfiler2()
11884 {
11885 if (m_Initialised)
11886 Shutdown();
11887 }
11888
11889 /**
11890 * Mongoose callback. Run in an arbitrary thread (possibly concurrently with other requests).
11891 */
11892 static void* MgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
11893 {
11894 CProfiler2* profiler = (CProfiler2*)request_info->user_data;
11895 ENSURE(profiler);
11896
11897 void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling
11898
11899 const char* header200 =
11900 "HTTP/1.1 200 OK\r\n"
11901 "Access-Control-Allow-Origin: *\r\n" // TODO: not great for security
11902 "Content-Type: text/plain; charset=utf-8\r\n\r\n";
11903
11904 const char* header404 =
11905 "HTTP/1.1 404 Not Found\r\n"
11906 "Content-Type: text/plain; charset=utf-8\r\n\r\n"
11907 "Unrecognised URI";
11908
11909 const char* header400 =
11910 "HTTP/1.1 400 Bad Request\r\n"
11911 "Content-Type: text/plain; charset=utf-8\r\n\r\n"
11912 "Invalid request";
11913
11914 switch (event)
11915 {
11916 case MG_NEW_REQUEST:
11917 {
11918 std::stringstream stream;
11919
11920 std::string uri = request_info->uri;
11921
11922 if (uri == "/download")
11923 {
11924 profiler->SaveToFile();
11925 }
11926 else if (uri == "/overview")
11927 {
11928 profiler->ConstructJSONOverview(stream);
11929 }
11930 else if (uri == "/query")
11931 {
11932 if (!request_info->query_string)
11933 {
11934 mg_printf(conn, "%s (no query string)", header400);
11935 return handled;
11936 }
11937
11938 // Identify the requested thread
11939 char buf[256];
11940 int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), "thread", buf, ARRAY_SIZE(buf));
11941 if (len < 0)
11942 {
11943 mg_printf(conn, "%s (no 'thread')", header400);
11944 return handled;
11945 }
11946 std::string thread(buf);
11947
11948 const char* err = profiler->ConstructJSONResponse(stream, thread);
11949 if (err)
11950 {
11951 mg_printf(conn, "%s (%s)", header400, err);
11952 return handled;
11953 }
11954 }
11955 else
11956 {
11957 mg_printf(conn, "%s", header404);
11958 return handled;
11959 }
11960
11961 mg_printf(conn, "%s", header200);
11962 std::string str = stream.str();
11963 mg_write(conn, str.c_str(), str.length());
11964 return handled;
11965 }
11966
11967 case MG_HTTP_ERROR:
11968 return NULL;
11969
11970 case MG_EVENT_LOG:
11971 // Called by Mongoose's cry()
11972 LOGERROR("Mongoose error: %s", request_info->log_message);
11973 return NULL;
11974
11975 case MG_INIT_SSL:
11976 return NULL;
11977
11978 default:
11979 debug_warn(L"Invalid Mongoose event type");
11980 return NULL;
11981 }
11982 };
11983
11984 void CProfiler2::Initialise()
11985 {
11986 ENSURE(!m_Initialised);
11987 m_Initialised = true;
11988
11989 RegisterCurrentThread("main");
11990 }
11991
11992 void CProfiler2::InitialiseGPU()
11993 {
11994 ENSURE(!m_GPU);
11995 m_GPU = new CProfiler2GPU(*this);
11996 }
11997
11998 void CProfiler2::EnableHTTP()
11999 {
12000 ENSURE(m_Initialised);
12001 LOGMESSAGERENDER("Starting profiler2 HTTP server");
12002
12003 // Ignore multiple enablings
12004 if (m_MgContext)
12005 return;
12006
12007 const char *options[] = {
12008 "listening_ports", "127.0.0.1:8000", // bind to localhost for security
12009 "num_threads", "6", // enough for the browser's parallel connection limit
12010 NULL
12011 };
12012 m_MgContext = mg_start(MgCallback, this, options);
12013 ENSURE(m_MgContext);
12014 }
12015
12016 void CProfiler2::EnableGPU()
12017 {
12018 ENSURE(m_Initialised);
12019 if (!m_GPU)
12020 {
12021 LOGMESSAGERENDER("Starting profiler2 GPU mode");
12022 InitialiseGPU();
12023 }
12024 }
12025
12026 void CProfiler2::ShutdownGPU()
12027 {
12028 LOGMESSAGERENDER("Shutting down profiler2 GPU mode");
12029 SAFE_DELETE(m_GPU);
12030 }
12031
12032 void CProfiler2::ShutDownHTTP()
12033 {
12034 LOGMESSAGERENDER("Shutting down profiler2 HTTP server");
12035 if (m_MgContext)
12036 {
12037 mg_stop(m_MgContext);
12038 m_MgContext = NULL;
12039 }
12040 }
12041
12042 void CProfiler2::Toggle()
12043 {
12044 // TODO: Maybe we can open the browser to the profiler page automatically
12045 if (m_GPU && m_MgContext)
12046 {
12047 ShutdownGPU();
12048 ShutDownHTTP();
12049 }
12050 else if (!m_GPU && !m_MgContext)
12051 {
12052 EnableGPU();
12053 EnableHTTP();
12054 }
12055 }
12056
12057 void CProfiler2::Shutdown()
12058 {
12059 ENSURE(m_Initialised);
12060
12061 ENSURE(!m_GPU); // must shutdown GPU before profiler
12062
12063 if (m_MgContext)
12064 {
12065 mg_stop(m_MgContext);
12066 m_MgContext = NULL;
12067 }
12068
12069 // the destructor is not called for the main thread
12070 // we have to call it manually to avoid memory leaks
12071 ENSURE(ThreadUtil::IsMainThread());
12072 m_Initialised = false;
12073 }
12074
12075 void CProfiler2::RecordGPUFrameStart()
12076 {
12077 if (m_GPU)
12078 m_GPU->FrameStart();
12079 }
12080
12081 void CProfiler2::RecordGPUFrameEnd()
12082 {
12083 if (m_GPU)
12084 m_GPU->FrameEnd();
12085 }
12086
12087 void CProfiler2::RecordGPURegionEnter(const char* id)
12088 {
12089 if (m_GPU)
12090 m_GPU->RegionEnter(id);
12091 }
12092
12093 void CProfiler2::RecordGPURegionLeave(const char* id)
12094 {
12095 if (m_GPU)
12096 m_GPU->RegionLeave(id);
12097 }
12098
12099 void CProfiler2::RegisterCurrentThread(const std::string& name)
12100 {
12101 ENSURE(m_Initialised);
12102
12103 // Must not register a thread more than once.
12104 ENSURE(m_CurrentStorage == nullptr);
12105
12106 m_CurrentStorage = new ThreadStorage(*this, name);
12107 AddThreadStorage(m_CurrentStorage);
12108
12109 RecordSyncMarker();
12110 RecordEvent("thread start");
12111 }
12112
12113 void CProfiler2::AddThreadStorage(ThreadStorage* storage)
12114 {
12115 std::lock_guard<std::mutex> lock(m_Mutex);
12116 m_Threads.push_back(std::unique_ptr<ThreadStorage>(storage));
12117 }
12118
12119 void CProfiler2::RemoveThreadStorage(ThreadStorage* storage)
12120 {
12121 std::lock_guard<std::mutex> lock(m_Mutex);
12122 m_Threads.erase(std::find_if(m_Threads.begin(), m_Threads.end(), [storage](const std::unique_ptr<ThreadStorage>& s) { return s.get() == storage; }));
12123 }
12124
12125 CProfiler2::ThreadStorage::ThreadStorage(CProfiler2& profiler, const std::string& name) :
12126 m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time()), m_HeldDepth(0)
12127 {
12128 m_Buffer = new u8[BUFFER_SIZE];
12129 memset(m_Buffer, ITEM_NOP, BUFFER_SIZE);
12130 }
12131
12132 CProfiler2::ThreadStorage::~ThreadStorage()
12133 {
12134 delete[] m_Buffer;
12135 }
12136
12137 void CProfiler2::ThreadStorage::Write(EItem type, const void* item, u32 itemSize)
12138 {
12139 if (m_HeldDepth > 0)
12140 {
12141 WriteHold(type, item, itemSize);
12142 return;
12143 }
12144 // See m_BufferPos0 etc for comments on synchronisation
12145
12146 u32 size = 1 + itemSize;
12147 u32 start = m_BufferPos0;
12148 if (start + size > BUFFER_SIZE)
12149 {
12150 // The remainder of the buffer is too small - fill the rest
12151 // with NOPs then start from offset 0, so we don't have to
12152 // bother splitting the real item across the end of the buffer
12153
12154 m_BufferPos0 = size;
12155 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
12156
12157 memset(m_Buffer + start, 0, BUFFER_SIZE - start);
12158 start = 0;
12159 }
12160 else
12161 {
12162 m_BufferPos0 = start + size;
12163 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
12164 }
12165
12166 m_Buffer[start] = (u8)type;
12167 memcpy(&m_Buffer[start + 1], item, itemSize);
12168
12169 COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer
12170 m_BufferPos1 = start + size;
12171 }
12172
12173 void CProfiler2::ThreadStorage::WriteHold(EItem type, const void* item, u32 itemSize)
12174 {
12175 u32 size = 1 + itemSize;
12176
12177 if (m_HoldBuffers[m_HeldDepth - 1].pos + size > CProfiler2::HOLD_BUFFER_SIZE)
12178 return; // we held on too much data, ignore the rest
12179
12180 m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos] = (u8)type;
12181 memcpy(&m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos + 1], item, itemSize);
12182
12183 m_HoldBuffers[m_HeldDepth - 1].pos += size;
12184 }
12185
12186 std::string CProfiler2::ThreadStorage::GetBuffer()
12187 {
12188 // Called from an arbitrary thread (not the one writing to the buffer).
12189 //
12190 // See comments on m_BufferPos0 etc.
12191
12192 shared_ptr<u8> buffer(new u8[BUFFER_SIZE], ArrayDeleter());
12193
12194 u32 pos1 = m_BufferPos1;
12195 COMPILER_FENCE; // must read m_BufferPos1 before m_Buffer
12196
12197 memcpy(buffer.get(), m_Buffer, BUFFER_SIZE);
12198
12199 COMPILER_FENCE; // must read m_BufferPos0 after m_Buffer
12200 u32 pos0 = m_BufferPos0;
12201
12202 // The range [pos1, pos0) modulo BUFFER_SIZE is invalid, so concatenate the rest of the buffer
12203
12204 if (pos1 <= pos0) // invalid range is in the middle of the buffer
12205 return std::string(buffer.get()+pos0, buffer.get()+BUFFER_SIZE) + std::string(buffer.get(), buffer.get()+pos1);
12206 else // invalid wrap is wrapped around the end/start buffer
12207 return std::string(buffer.get()+pos0, buffer.get()+pos1);
12208 }
12209
12210 void CProfiler2::ThreadStorage::RecordAttribute(const char* fmt, va_list argp)
12211 {
12212 char buffer[MAX_ATTRIBUTE_LENGTH + 4] = {0}; // first 4 bytes are used for storing length
12213 int len = vsnprintf(buffer + 4, MAX_ATTRIBUTE_LENGTH - 1, fmt, argp); // subtract 1 from length to make MSVC vsnprintf safe
12214 // (Don't use vsprintf_s because it treats overflow as fatal)
12215
12216 // Terminate the string if the printing was truncated
12217 if (len < 0 || len >= (int)MAX_ATTRIBUTE_LENGTH - 1)
12218 {
12219 strncpy(buffer + 4 + MAX_ATTRIBUTE_LENGTH - 4, "...", 4);
12220 len = MAX_ATTRIBUTE_LENGTH - 1; // excluding null terminator
12221 }
12222
12223 // Store the length in the buffer
12224 memcpy(buffer, &len, sizeof(len));
12225
12226 Write(ITEM_ATTRIBUTE, buffer, 4 + len);
12227 }
12228
12229 size_t CProfiler2::ThreadStorage::HoldLevel()
12230 {
12231 return m_HeldDepth;
12232 }
12233
12234 u8 CProfiler2::ThreadStorage::HoldType()
12235 {
12236 return m_HoldBuffers[m_HeldDepth - 1].type;
12237 }
12238
12239 void CProfiler2::ThreadStorage::PutOnHold(u8 newType)
12240 {
12241 m_HeldDepth++;
12242 m_HoldBuffers[m_HeldDepth - 1].clear();
12243 m_HoldBuffers[m_HeldDepth - 1].setType(newType);
12244 }
12245
12246 // this flattens the stack, use it sensibly
12247 void rewriteBuffer(u8* buffer, u32& bufferSize)
12248 {
12249 double startTime = timer_Time();
12250
12251 u32 size = bufferSize;
12252 u32 readPos = 0;
12253
12254 double initialTime = -1;
12255 double total_time = -1;
12256 const char* regionName;
12257 std::set<std::string> topLevelArgs;
12258
12259 using infoPerType = std::tuple<const char*, double, std::set<std::string> >;
12260 using timeByTypeMap = std::unordered_map<std::string, infoPerType>;
12261
12262 timeByTypeMap timeByType;
12263 std::vector<double> last_time_stack;
12264 std::vector<const char*> last_names;
12265
12266 // never too many hacks
12267 std::string current_attribute = "";
12268 std::map<std::string, double> time_per_attribute;
12269
12270 // Let's read the first event
12271 {
12272 u8 type = buffer[readPos];
12273 ++readPos;
12274 if (type != CProfiler2::ITEM_ENTER)
12275 {
12276 debug_warn("Profiler2: Condensing a region should run into ITEM_ENTER first");
12277 return; // do nothing
12278 }
12279 CProfiler2::SItem_dt_id item;
12280 memcpy(&item, buffer + readPos, sizeof(item));
12281 readPos += sizeof(item);
12282
12283 regionName = item.id;
12284 last_names.push_back(item.id);
12285 initialTime = (double)item.dt;
12286 }
12287 int enter = 1;
12288 int leaves = 0;
12289 // Read subsequent events. Flatten hierarchy because it would get too complicated otherwise.
12290 // To make sure time doesn't bloat, subtract time from nested events
12291 while (readPos < size)
12292 {
12293 u8 type = buffer[readPos];
12294 ++readPos;
12295
12296 switch (type)
12297 {
12298 case CProfiler2::ITEM_NOP:
12299 {
12300 // ignore
12301 break;
12302 }
12303 case CProfiler2::ITEM_SYNC:
12304 {
12305 debug_warn("Aggregated regions should not be used across frames");
12306 // still try to act sane
12307 readPos += sizeof(double);
12308 readPos += sizeof(CProfiler2::RESYNC_MAGIC);
12309 break;
12310 }
12311 case CProfiler2::ITEM_EVENT:
12312 {
12313 // skip for now
12314 readPos += sizeof(CProfiler2::SItem_dt_id);
12315 break;
12316 }
12317 case CProfiler2::ITEM_ENTER:
12318 {
12319 enter++;
12320 CProfiler2::SItem_dt_id item;
12321 memcpy(&item, buffer + readPos, sizeof(item));
12322 readPos += sizeof(item);
12323 last_time_stack.push_back((double)item.dt);
12324 last_names.push_back(item.id);
12325 current_attribute = "";
12326 break;
12327 }
12328 case CProfiler2::ITEM_LEAVE:
12329 {
12330 float item_time;
12331 memcpy(&item_time, buffer + readPos, sizeof(float));
12332 readPos += sizeof(float);
12333
12334 leaves++;
12335 if (last_names.empty())
12336 {
12337 // we somehow lost the first entry in the process
12338 debug_warn("Invalid buffer for condensing");
12339 }
12340 const char* item_name = last_names.back();
12341 last_names.pop_back();
12342
12343 if (last_time_stack.empty())
12344 {
12345 // this is the leave for the whole scope
12346 total_time = (double)item_time;
12347 break;
12348 }
12349 double time = (double)item_time - last_time_stack.back();
12350
12351 std::string name = std::string(item_name);
12352 timeByTypeMap::iterator TimeForType = timeByType.find(name);
12353 if (TimeForType == timeByType.end())
12354 {
12355 // keep reference to the original char pointer to make sure we don't break things down the line
12356 std::get<0>(timeByType[name]) = item_name;
12357 std::get<1>(timeByType[name]) = 0;
12358 }
12359 std::get<1>(timeByType[name]) += time;
12360
12361 last_time_stack.pop_back();
12362 // if we were nested, subtract our time from the below scope by making it look like it starts later
12363 if (!last_time_stack.empty())
12364 last_time_stack.back() += time;
12365
12366 if (!current_attribute.empty())
12367 {
12368 time_per_attribute[current_attribute] += time;
12369 }
12370
12371 break;
12372 }
12373 case CProfiler2::ITEM_ATTRIBUTE:
12374 {
12375 // skip for now
12376 u32 len;
12377 memcpy(&len, buffer + readPos, sizeof(len));
12378 ENSURE(len <= CProfiler2::MAX_ATTRIBUTE_LENGTH);
12379 readPos += sizeof(len);
12380
12381 char message[CProfiler2::MAX_ATTRIBUTE_LENGTH] = {0};
12382 memcpy(&message[0], buffer + readPos, std::min(size_t(len), CProfiler2::MAX_ATTRIBUTE_LENGTH));
12383 CStr mess = CStr((const char*)message, len);
12384 if (!last_names.empty())
12385 {
12386 timeByTypeMap::iterator it = timeByType.find(std::string(last_names.back()));
12387 if (it == timeByType.end())
12388 topLevelArgs.insert(mess);
12389 else
12390 std::get<2>(timeByType[std::string(last_names.back())]).insert(mess);
12391 }
12392 readPos += len;
12393 current_attribute = mess;
12394 break;
12395 }
12396 default:
12397 debug_warn(L"Invalid profiler item when condensing buffer");
12398 continue;
12399 }
12400 }
12401
12402 // rewrite the buffer
12403 // what we rewrite will always be smaller than the current buffer's size
12404 u32 writePos = 0;
12405 double curTime = initialTime;
12406 // the region enter
12407 {
12408 CProfiler2::SItem_dt_id item = { (float)curTime, regionName };
12409 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
12410 memcpy(buffer + writePos + 1, &item, sizeof(item));
12411 writePos += sizeof(item) + 1;
12412 // add a nanosecond for sanity
12413 curTime += 0.000001;
12414 }
12415 // sub-events, aggregated
12416 for (const std::pair<std::string, infoPerType>& type : timeByType)
12417 {
12418 CProfiler2::SItem_dt_id item = { (float)curTime, std::get<0>(type.second) };
12419 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
12420 memcpy(buffer + writePos + 1, &item, sizeof(item));
12421 writePos += sizeof(item) + 1;
12422
12423 // write relevant attributes if present
12424 for (const std::string& attrib : std::get<2>(type.second))
12425 {
12426 buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE;
12427 writePos++;
12428 std::string basic = attrib;
12429 std::map<std::string, double>::iterator time_attrib = time_per_attribute.find(attrib);
12430 if (time_attrib != time_per_attribute.end())
12431 basic += " " + CStr::FromInt(1000000*time_attrib->second) + "us";
12432
12433 u32 length = basic.size();
12434 memcpy(buffer + writePos, &length, sizeof(length));
12435 writePos += sizeof(length);
12436 memcpy(buffer + writePos, basic.c_str(), length);
12437 writePos += length;
12438 }
12439
12440 curTime += std::get<1>(type.second);
12441
12442 float leave_time = (float)curTime;
12443 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
12444 memcpy(buffer + writePos + 1, &leave_time, sizeof(float));
12445 writePos += sizeof(float) + 1;
12446 }
12447 // Time of computation
12448 {
12449 CProfiler2::SItem_dt_id item = { (float)curTime, "CondenseBuffer" };
12450 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
12451 memcpy(buffer + writePos + 1, &item, sizeof(item));
12452 writePos += sizeof(item) + 1;
12453 }
12454 {
12455 float time_out = (float)(curTime + timer_Time() - startTime);
12456 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
12457 memcpy(buffer + writePos + 1, &time_out, sizeof(float));
12458 writePos += sizeof(float) + 1;
12459 // add a nanosecond for sanity
12460 curTime += 0.000001;
12461 }
12462
12463 // the region leave
12464 {
12465 if (total_time < 0)
12466 {
12467 total_time = curTime + 0.000001;
12468
12469 buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE;
12470 writePos++;
12471 u32 length = sizeof("buffer overflow");
12472 memcpy(buffer + writePos, &length, sizeof(length));
12473 writePos += sizeof(length);
12474 memcpy(buffer + writePos, "buffer overflow", length);
12475 writePos += length;
12476 }
12477 else if (total_time < curTime)
12478 {
12479 // this seems to happen on rare occasions.
12480 curTime = total_time;
12481 }
12482 float leave_time = (float)total_time;
12483 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
12484 memcpy(buffer + writePos + 1, &leave_time, sizeof(float));
12485 writePos += sizeof(float) + 1;
12486 }
12487 bufferSize = writePos;
12488 }
12489
12490 void CProfiler2::ThreadStorage::HoldToBuffer(bool condensed)
12491 {
12492 ENSURE(m_HeldDepth);
12493 if (condensed)
12494 {
12495 // rewrite the buffer to show aggregated data
12496 rewriteBuffer(m_HoldBuffers[m_HeldDepth - 1].buffer, m_HoldBuffers[m_HeldDepth - 1].pos);
12497 }
12498
12499 if (m_HeldDepth > 1)
12500 {
12501 // copy onto buffer below
12502 HoldBuffer& copied = m_HoldBuffers[m_HeldDepth - 1];
12503 HoldBuffer& target = m_HoldBuffers[m_HeldDepth - 2];
12504 if (target.pos + copied.pos > HOLD_BUFFER_SIZE)
12505 return; // too much data, too bad
12506
12507 memcpy(&target.buffer[target.pos], copied.buffer, copied.pos);
12508
12509 target.pos += copied.pos;
12510 }
12511 else
12512 {
12513 u32 size = m_HoldBuffers[m_HeldDepth - 1].pos;
12514 u32 start = m_BufferPos0;
12515 if (start + size > BUFFER_SIZE)
12516 {
12517 m_BufferPos0 = size;
12518 COMPILER_FENCE;
12519 memset(m_Buffer + start, 0, BUFFER_SIZE - start);
12520 start = 0;
12521 }
12522 else
12523 {
12524 m_BufferPos0 = start + size;
12525 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
12526 }
12527 memcpy(&m_Buffer[start], m_HoldBuffers[m_HeldDepth - 1].buffer, size);
12528 COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer
12529 m_BufferPos1 = start + size;
12530 }
12531 m_HeldDepth--;
12532 }
12533 void CProfiler2::ThreadStorage::ThrowawayHoldBuffer()
12534 {
12535 if (!m_HeldDepth)
12536 return;
12537 m_HeldDepth--;
12538 }
12539
12540 void CProfiler2::ConstructJSONOverview(std::ostream& stream)
12541 {
12542 TIMER(L"profile2 overview");
12543
12544 std::lock_guard<std::mutex> lock(m_Mutex);
12545
12546 stream << "{\"threads\":[";
12547 bool first_time = true;
12548 for (std::unique_ptr<ThreadStorage>& storage : m_Threads)
12549 {
12550 if (!first_time)
12551 stream << ",";
12552 stream << "{\"name\":\"" << CStr(storage->GetName()).EscapeToPrintableASCII() << "\"}";
12553 first_time = false;
12554 }
12555 stream << "]}";
12556 }
12557
12558 /**
12559 * Given a buffer and a visitor class (with functions OnEvent, OnEnter, OnLeave, OnAttribute),
12560 * calls the visitor for every item in the buffer.
12561 */
12562 template<typename V>
12563 void RunBufferVisitor(const std::string& buffer, V& visitor)
12564 {
12565 TIMER(L"profile2 visitor");
12566
12567 // The buffer doesn't necessarily start at the beginning of an item
12568 // (we just grabbed it from some arbitrary point in the middle),
12569 // so scan forwards until we find a sync marker.
12570 // (This is probably pretty inefficient.)
12571
12572 u32 realStart = (u32)-1; // the start point decided by the scan algorithm
12573
12574 for (u32 start = 0; start + 1 + sizeof(CProfiler2::RESYNC_MAGIC) <= buffer.length(); ++start)
12575 {
12576 if (buffer[start] == CProfiler2::ITEM_SYNC
12577 && memcmp(buffer.c_str() + start + 1, &CProfiler2::RESYNC_MAGIC, sizeof(CProfiler2::RESYNC_MAGIC)) == 0)
12578 {
12579 realStart = start;
12580 break;
12581 }
12582 }
12583
12584 ENSURE(realStart != (u32)-1); // we should have found a sync point somewhere in the buffer
12585
12586 u32 pos = realStart; // the position as we step through the buffer
12587
12588 double lastTime = -1;
12589 // set to non-negative by EVENT_SYNC; we ignore all items before that
12590 // since we can't compute their absolute times
12591
12592 while (pos < buffer.length())
12593 {
12594 u8 type = buffer[pos];
12595 ++pos;
12596
12597 switch (type)
12598 {
12599 case CProfiler2::ITEM_NOP:
12600 {
12601 // ignore
12602 break;
12603 }
12604 case CProfiler2::ITEM_SYNC:
12605 {
12606 u8 magic[sizeof(CProfiler2::RESYNC_MAGIC)];
12607 double t;
12608 memcpy(magic, buffer.c_str()+pos, ARRAY_SIZE(magic));
12609 ENSURE(memcmp(magic, &CProfiler2::RESYNC_MAGIC, sizeof(CProfiler2::RESYNC_MAGIC)) == 0);
12610 pos += sizeof(CProfiler2::RESYNC_MAGIC);
12611 memcpy(&t, buffer.c_str()+pos, sizeof(t));
12612 pos += sizeof(t);
12613 lastTime = t;
12614 visitor.OnSync(lastTime);
12615 break;
12616 }
12617 case CProfiler2::ITEM_EVENT:
12618 {
12619 CProfiler2::SItem_dt_id item;
12620 memcpy(&item, buffer.c_str()+pos, sizeof(item));
12621 pos += sizeof(item);
12622 if (lastTime >= 0)
12623 {
12624 visitor.OnEvent(lastTime + (double)item.dt, item.id);
12625 }
12626 break;
12627 }
12628 case CProfiler2::ITEM_ENTER:
12629 {
12630 CProfiler2::SItem_dt_id item;
12631 memcpy(&item, buffer.c_str()+pos, sizeof(item));
12632 pos += sizeof(item);
12633 if (lastTime >= 0)
12634 {
12635 visitor.OnEnter(lastTime + (double)item.dt, item.id);
12636 }
12637 break;
12638 }
12639 case CProfiler2::ITEM_LEAVE:
12640 {
12641 float leave_time;
12642 memcpy(&leave_time, buffer.c_str() + pos, sizeof(float));
12643 pos += sizeof(float);
12644 if (lastTime >= 0)
12645 {
12646 visitor.OnLeave(lastTime + (double)leave_time);
12647 }
12648 break;
12649 }
12650 case CProfiler2::ITEM_ATTRIBUTE:
12651 {
12652 u32 len;
12653 memcpy(&len, buffer.c_str()+pos, sizeof(len));
12654 ENSURE(len <= CProfiler2::MAX_ATTRIBUTE_LENGTH);
12655 pos += sizeof(len);
12656 std::string attribute(buffer.c_str()+pos, buffer.c_str()+pos+len);
12657 pos += len;
12658 if (lastTime >= 0)
12659 {
12660 visitor.OnAttribute(attribute);
12661 }
12662 break;
12663 }
12664 default:
12665 debug_warn(L"Invalid profiler item when parsing buffer");
12666 return;
12667 }
12668 }
12669 };
12670
12671 /**
12672 * Visitor class that dumps events as JSON.
12673 * TODO: this is pretty inefficient (in implementation and in output format).
12674 */
12675 struct BufferVisitor_Dump
12676 {
12677 NONCOPYABLE(BufferVisitor_Dump);
12678 public:
12679 BufferVisitor_Dump(std::ostream& stream) : m_Stream(stream)
12680 {
12681 }
12682
12683 void OnSync(double UNUSED(time))
12684 {
12685 // Split the array of items into an array of array (arbitrarily splitting
12686 // around the sync points) to avoid array-too-large errors in JSON decoders
12687 m_Stream << "null], [\n";
12688 }
12689
12690 void OnEvent(double time, const char* id)
12691 {
12692 m_Stream << "[1," << std::fixed << std::setprecision(9) << time;
12693 m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n";
12694 }
12695
12696 void OnEnter(double time, const char* id)
12697 {
12698 m_Stream << "[2," << std::fixed << std::setprecision(9) << time;
12699 m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n";
12700 }
12701
12702 void OnLeave(double time)
12703 {
12704 m_Stream << "[3," << std::fixed << std::setprecision(9) << time << "],\n";
12705 }
12706
12707 void OnAttribute(const std::string& attr)
12708 {
12709 m_Stream << "[4,\"" << CStr(attr).EscapeToPrintableASCII() << "\"],\n";
12710 }
12711
12712 std::ostream& m_Stream;
12713 };
12714
12715 const char* CProfiler2::ConstructJSONResponse(std::ostream& stream, const std::string& thread)
12716 {
12717 TIMER(L"profile2 query");
12718
12719 std::string buffer;
12720
12721 {
12722 TIMER(L"profile2 get buffer");
12723
12724 std::lock_guard<std::mutex> lock(m_Mutex); // lock against changes to m_Threads or deletions of ThreadStorage
12725
12726 std::vector<std::unique_ptr<ThreadStorage>>::iterator it =
12727 std::find_if(m_Threads.begin(), m_Threads.end(), [&thread](std::unique_ptr<ThreadStorage>& storage) {
12728 return storage->GetName() == thread;
12729 });
12730
12731 if (it == m_Threads.end())
12732 return "cannot find named thread";
12733
12734 stream << "{\"events\":[\n";
12735
12736 stream << "[\n";
12737 buffer = (*it)->GetBuffer();
12738 }
12739
12740 BufferVisitor_Dump visitor(stream);
12741 RunBufferVisitor(buffer, visitor);
12742
12743 stream << "null]\n]}";
12744
12745 return NULL;
12746 }
12747
12748 void CProfiler2::SaveToFile()
12749 {
12750 OsPath path = psLogDir()/"profile2.jsonp";
12751 std::ofstream stream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
12752 ENSURE(stream.good());
12753
12754 stream << "profileDataCB({\"threads\": [\n";
12755 bool first_time = true;
12756 for (std::unique_ptr<ThreadStorage>& storage : m_Threads)
12757 {
12758 if (!first_time)
12759 stream << ",\n";
12760 stream << "{\"name\":\"" << CStr(storage->GetName()).EscapeToPrintableASCII() << "\",\n";
12761 stream << "\"data\": ";
12762 ConstructJSONResponse(stream, storage->GetName());
12763 stream << "\n}";
12764 first_time = false;
12765 }
12766 stream << "\n]});\n";
12767 }
12768
12769 CProfile2SpikeRegion::CProfile2SpikeRegion(const char* name, double spikeLimit) :
12770 m_Name(name), m_Limit(spikeLimit), m_PushedHold(true)
12771 {
12772 if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE)
12773 g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_SPIKE);
12774 else
12775 m_PushedHold = false;
12776 COMPILER_FENCE;
12777 g_Profiler2.RecordRegionEnter(m_Name);
12778 m_StartTime = g_Profiler2.GetTime();
12779 }
12780 CProfile2SpikeRegion::~CProfile2SpikeRegion()
12781 {
12782 double time = g_Profiler2.GetTime();
12783 g_Profiler2.RecordRegionLeave();
12784 bool shouldWrite = time - m_StartTime > m_Limit;
12785
12786 if (m_PushedHold)
12787 g_Profiler2.StopHoldingMessages(shouldWrite);
12788 }
12789
12790 CProfile2AggregatedRegion::CProfile2AggregatedRegion(const char* name, double spikeLimit) :
12791 m_Name(name), m_Limit(spikeLimit), m_PushedHold(true)
12792 {
12793 if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE)
12794 g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_AGGREGATE);
12795 else
12796 m_PushedHold = false;
12797 COMPILER_FENCE;
12798 g_Profiler2.RecordRegionEnter(m_Name);
12799 m_StartTime = g_Profiler2.GetTime();
12800 }
12801 CProfile2AggregatedRegion::~CProfile2AggregatedRegion()
12802 {
12803 double time = g_Profiler2.GetTime();
12804 g_Profiler2.RecordRegionLeave();
12805 bool shouldWrite = time - m_StartTime > m_Limit;
12806
12807 if (m_PushedHold)
12808 g_Profiler2.StopHoldingMessages(shouldWrite, true);
12809 }
12810Index: source/ps/Replay.h
12811===================================================================
12812--- source/ps/Replay.h (revision 23275)
12813+++ source/ps/Replay.h (working copy)
12814@@ -1,118 +1,119 @@
12815 /* Copyright (C) 2019 Wildfire Games.
12816 * This file is part of 0 A.D.
12817 *
12818 * 0 A.D. is free software: you can redistribute it and/or modify
12819 * it under the terms of the GNU General Public License as published by
12820 * the Free Software Foundation, either version 2 of the License, or
12821 * (at your option) any later version.
12822 *
12823 * 0 A.D. is distributed in the hope that it will be useful,
12824 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12825 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12826 * GNU General Public License for more details.
12827 *
12828 * You should have received a copy of the GNU General Public License
12829 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
12830 */
12831
12832 #ifndef INCLUDED_REPLAY
12833 #define INCLUDED_REPLAY
12834
12835+#include "lib/os_path.h"
12836 #include "ps/CStr.h"
12837 #include "scriptinterface/ScriptTypes.h"
12838
12839 struct SimulationCommand;
12840 class CSimulation2;
12841 class ScriptInterface;
12842
12843 /**
12844 * Replay log recorder interface.
12845 * Call its methods at appropriate times during the game.
12846 */
12847 class IReplayLogger
12848 {
12849 public:
12850 IReplayLogger() { }
12851 virtual ~IReplayLogger() { }
12852
12853 /**
12854 * Started the game with the given game attributes.
12855 */
12856 virtual void StartGame(JS::MutableHandleValue attribs) = 0;
12857
12858 /**
12859 * Run the given turn with the given collection of player commands.
12860 */
12861 virtual void Turn(u32 n, u32 turnLength, std::vector<SimulationCommand>& commands) = 0;
12862
12863 /**
12864 * Optional hash of simulation state (for sync checking).
12865 */
12866 virtual void Hash(const std::string& hash, bool quick) = 0;
12867
12868 /**
12869 * Saves metadata.json containing part of the simulation state used for the summary screen.
12870 */
12871 virtual void SaveMetadata(const CSimulation2& simulation) = 0;
12872
12873 /**
12874 * Remember the directory containing the commands.txt file, so that we can save additional files to it.
12875 */
12876 virtual OsPath GetDirectory() const = 0;
12877 };
12878
12879 /**
12880 * Implementation of IReplayLogger that simply throws away all data.
12881 */
12882 class CDummyReplayLogger : public IReplayLogger
12883 {
12884 public:
12885 virtual void StartGame(JS::MutableHandleValue UNUSED(attribs)) { }
12886 virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), std::vector<SimulationCommand>& UNUSED(commands)) { }
12887 virtual void Hash(const std::string& UNUSED(hash), bool UNUSED(quick)) { }
12888 virtual void SaveMetadata(const CSimulation2& UNUSED(simulation)) { };
12889 virtual OsPath GetDirectory() const { return OsPath(); }
12890 };
12891
12892 /**
12893 * Implementation of IReplayLogger that saves data to a file in the logs directory.
12894 */
12895 class CReplayLogger : public IReplayLogger
12896 {
12897 NONCOPYABLE(CReplayLogger);
12898 public:
12899 CReplayLogger(const ScriptInterface& scriptInterface);
12900 ~CReplayLogger();
12901
12902 virtual void StartGame(JS::MutableHandleValue attribs);
12903 virtual void Turn(u32 n, u32 turnLength, std::vector<SimulationCommand>& commands);
12904 virtual void Hash(const std::string& hash, bool quick);
12905 virtual void SaveMetadata(const CSimulation2& simulation);
12906 virtual OsPath GetDirectory() const;
12907
12908 private:
12909 const ScriptInterface& m_ScriptInterface;
12910 std::ostream* m_Stream;
12911 OsPath m_Directory;
12912 };
12913
12914 /**
12915 * Replay log replayer. Runs the log with no graphics and dumps some info to stdout.
12916 */
12917 class CReplayPlayer
12918 {
12919 public:
12920 CReplayPlayer();
12921 ~CReplayPlayer();
12922
12923 void Load(const OsPath& path);
12924 void Replay(const bool serializationtest, const int rejointestturn, const bool ooslog, const bool testHashFull, const bool testHashQuick);
12925
12926 private:
12927 std::istream* m_Stream;
12928 CStr ModListToString(const std::vector<std::vector<CStr>>& list) const;
12929 void CheckReplayMods(const ScriptInterface& scriptInterface, JS::HandleValue attribs) const;
12930 void TestHash(const std::string& hashType, const std::string& replayHash, const bool testHashFull, const bool testHashQuick);
12931 };
12932
12933 #endif // INCLUDED_REPLAY
12934Index: source/ps/UserReport.cpp
12935===================================================================
12936--- source/ps/UserReport.cpp (revision 23275)
12937+++ source/ps/UserReport.cpp (working copy)
12938@@ -1,636 +1,637 @@
12939 /* Copyright (C) 2019 Wildfire Games.
12940 * This file is part of 0 A.D.
12941 *
12942 * 0 A.D. is free software: you can redistribute it and/or modify
12943 * it under the terms of the GNU General Public License as published by
12944 * the Free Software Foundation, either version 2 of the License, or
12945 * (at your option) any later version.
12946 *
12947 * 0 A.D. is distributed in the hope that it will be useful,
12948 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12949 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12950 * GNU General Public License for more details.
12951 *
12952 * You should have received a copy of the GNU General Public License
12953 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
12954 */
12955
12956 #include "precompiled.h"
12957
12958 #include "UserReport.h"
12959
12960 #include "lib/timer.h"
12961 #include "lib/utf8.h"
12962 #include "lib/external_libraries/curl.h"
12963 #include "lib/external_libraries/zlib.h"
12964 #include "lib/file/archive/stream.h"
12965 #include "lib/os_path.h"
12966 #include "lib/sysdep/sysdep.h"
12967 #include "ps/ConfigDB.h"
12968 #include "ps/Filesystem.h"
12969 #include "ps/Profiler2.h"
12970+#include "ps/Pyrogenesis.h"
12971
12972 #include <condition_variable>
12973 #include <fstream>
12974 #include <mutex>
12975 #include <string>
12976 #include <thread>
12977
12978 #define DEBUG_UPLOADS 0
12979
12980 /*
12981 * The basic idea is that the game submits reports to us, which we send over
12982 * HTTP to a server for storage and analysis.
12983 *
12984 * We can't use libcurl's asynchronous 'multi' API, because DNS resolution can
12985 * be synchronous and slow (which would make the game pause).
12986 * So we use the 'easy' API in a background thread.
12987 * The main thread submits reports, toggles whether uploading is enabled,
12988 * and polls for the current status (typically to display in the GUI);
12989 * the worker thread does all of the uploading.
12990 *
12991 * It'd be nice to extend this in the future to handle things like crash reports.
12992 * The game should store the crashlogs (suitably anonymised) in a directory, and
12993 * we should detect those files and upload them when we're restarted and online.
12994 */
12995
12996
12997 /**
12998 * Version number stored in config file when the user agrees to the reporting.
12999 * Reporting will be disabled if the config value is missing or is less than
13000 * this value. If we start reporting a lot more data, we should increase this
13001 * value and get the user to re-confirm.
13002 */
13003 static const int REPORTER_VERSION = 1;
13004
13005 /**
13006 * Time interval (seconds) at which the worker thread will check its reconnection
13007 * timers. (This should be relatively high so the thread doesn't waste much time
13008 * continually waking up.)
13009 */
13010 static const double TIMER_CHECK_INTERVAL = 10.0;
13011
13012 /**
13013 * Seconds we should wait before reconnecting to the server after a failure.
13014 */
13015 static const double RECONNECT_INVERVAL = 60.0;
13016
13017 CUserReporter g_UserReporter;
13018
13019 struct CUserReport
13020 {
13021 time_t m_Time;
13022 std::string m_Type;
13023 int m_Version;
13024 std::string m_Data;
13025 };
13026
13027 class CUserReporterWorker
13028 {
13029 public:
13030 CUserReporterWorker(const std::string& userID, const std::string& url) :
13031 m_URL(url), m_UserID(userID), m_Enabled(false), m_Shutdown(false), m_Status("disabled"),
13032 m_PauseUntilTime(timer_Time()), m_LastUpdateTime(timer_Time())
13033 {
13034 // Set up libcurl:
13035
13036 m_Curl = curl_easy_init();
13037 ENSURE(m_Curl);
13038
13039 #if DEBUG_UPLOADS
13040 curl_easy_setopt(m_Curl, CURLOPT_VERBOSE, 1L);
13041 #endif
13042
13043 // Capture error messages
13044 curl_easy_setopt(m_Curl, CURLOPT_ERRORBUFFER, m_ErrorBuffer);
13045
13046 // Disable signal handlers (required for multithreaded applications)
13047 curl_easy_setopt(m_Curl, CURLOPT_NOSIGNAL, 1L);
13048
13049 // To minimise security risks, don't support redirects
13050 curl_easy_setopt(m_Curl, CURLOPT_FOLLOWLOCATION, 0L);
13051
13052 // Prevent this thread from blocking the engine shutdown for 5 minutes in case the server is unavailable
13053 curl_easy_setopt(m_Curl, CURLOPT_CONNECTTIMEOUT, 10L);
13054
13055 // Set IO callbacks
13056 curl_easy_setopt(m_Curl, CURLOPT_WRITEFUNCTION, ReceiveCallback);
13057 curl_easy_setopt(m_Curl, CURLOPT_WRITEDATA, this);
13058 curl_easy_setopt(m_Curl, CURLOPT_READFUNCTION, SendCallback);
13059 curl_easy_setopt(m_Curl, CURLOPT_READDATA, this);
13060
13061 // Set URL to POST to
13062 curl_easy_setopt(m_Curl, CURLOPT_URL, url.c_str());
13063 curl_easy_setopt(m_Curl, CURLOPT_POST, 1L);
13064
13065 // Set up HTTP headers
13066 m_Headers = NULL;
13067 // Set the UA string
13068 std::string ua = "User-Agent: 0ad ";
13069 ua += curl_version();
13070 ua += " (http://play0ad.com/)";
13071 m_Headers = curl_slist_append(m_Headers, ua.c_str());
13072 // Override the default application/x-www-form-urlencoded type since we're not using that type
13073 m_Headers = curl_slist_append(m_Headers, "Content-Type: application/octet-stream");
13074 // Disable the Accept header because it's a waste of a dozen bytes
13075 m_Headers = curl_slist_append(m_Headers, "Accept: ");
13076 curl_easy_setopt(m_Curl, CURLOPT_HTTPHEADER, m_Headers);
13077
13078 m_WorkerThread = std::thread(RunThread, this);
13079 }
13080
13081 ~CUserReporterWorker()
13082 {
13083 curl_slist_free_all(m_Headers);
13084 curl_easy_cleanup(m_Curl);
13085 }
13086
13087 /**
13088 * Called by main thread, when the online reporting is enabled/disabled.
13089 */
13090 void SetEnabled(bool enabled)
13091 {
13092 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13093 if (enabled != m_Enabled)
13094 {
13095 m_Enabled = enabled;
13096
13097 // Wake up the worker thread
13098 m_WorkerCV.notify_all();
13099 }
13100 }
13101
13102 /**
13103 * Called by main thread to request shutdown.
13104 * Returns true if we've shut down successfully.
13105 * Returns false if shutdown is taking too long (we might be blocked on a
13106 * sync network operation) - you mustn't destroy this object, just leak it
13107 * and terminate.
13108 */
13109 bool Shutdown()
13110 {
13111 {
13112 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13113 m_Shutdown = true;
13114 }
13115
13116 // Wake up the worker thread
13117 m_WorkerCV.notify_all();
13118
13119 // Wait for it to shut down cleanly
13120 // TODO: should have a timeout in case of network hangs
13121 m_WorkerThread.join();
13122
13123 return true;
13124 }
13125
13126 /**
13127 * Called by main thread to determine the current status of the uploader.
13128 */
13129 std::string GetStatus()
13130 {
13131 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13132 return m_Status;
13133 }
13134
13135 /**
13136 * Called by main thread to add a new report to the queue.
13137 */
13138 void Submit(const shared_ptr<CUserReport>& report)
13139 {
13140 {
13141 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13142 m_ReportQueue.push_back(report);
13143 }
13144
13145 // Wake up the worker thread
13146 m_WorkerCV.notify_all();
13147 }
13148
13149 /**
13150 * Called by the main thread every frame, so we can check
13151 * retransmission timers.
13152 */
13153 void Update()
13154 {
13155 double now = timer_Time();
13156 if (now > m_LastUpdateTime + TIMER_CHECK_INTERVAL)
13157 {
13158 // Wake up the worker thread
13159 m_WorkerCV.notify_all();
13160
13161 m_LastUpdateTime = now;
13162 }
13163 }
13164
13165 private:
13166 static void RunThread(CUserReporterWorker* data)
13167 {
13168 debug_SetThreadName("CUserReportWorker");
13169 g_Profiler2.RegisterCurrentThread("userreport");
13170
13171 data->Run();
13172 }
13173
13174 void Run()
13175 {
13176 // Set libcurl's proxy configuration
13177 // (This has to be done in the thread because it's potentially very slow)
13178 SetStatus("proxy");
13179 std::wstring proxy;
13180
13181 {
13182 PROFILE2("get proxy config");
13183 if (sys_get_proxy_config(wstring_from_utf8(m_URL), proxy) == INFO::OK)
13184 curl_easy_setopt(m_Curl, CURLOPT_PROXY, utf8_from_wstring(proxy).c_str());
13185 }
13186
13187 SetStatus("waiting");
13188
13189 /*
13190 * We use a condition_variable to let the thread be woken up when it has
13191 * work to do. Various actions from the main thread can wake it:
13192 * * SetEnabled()
13193 * * Shutdown()
13194 * * Submit()
13195 * * Retransmission timeouts, once every several seconds
13196 *
13197 * If multiple actions have triggered wakeups, we might respond to
13198 * all of those actions after the first wakeup, which is okay (we'll do
13199 * nothing during the subsequent wakeups). We should never hang due to
13200 * processing fewer actions than wakeups.
13201 *
13202 * Retransmission timeouts are triggered via the main thread.
13203 */
13204
13205 // Wait until the main thread wakes us up
13206 while (true)
13207 {
13208 g_Profiler2.RecordRegionEnter("condition_variable wait");
13209
13210 std::unique_lock<std::mutex> lock(m_WorkerMutex);
13211 m_WorkerCV.wait(lock);
13212 lock.unlock();
13213
13214 g_Profiler2.RecordRegionLeave();
13215
13216 // Handle shutdown requests as soon as possible
13217 if (GetShutdown())
13218 return;
13219
13220 // If we're not enabled, ignore this wakeup
13221 if (!GetEnabled())
13222 continue;
13223
13224 // If we're still pausing due to a failed connection,
13225 // go back to sleep again
13226 if (timer_Time() < m_PauseUntilTime)
13227 continue;
13228
13229 // We're enabled, so process as many reports as possible
13230 while (ProcessReport())
13231 {
13232 // Handle shutdowns while we were sending the report
13233 if (GetShutdown())
13234 return;
13235 }
13236 }
13237 }
13238
13239 bool GetEnabled()
13240 {
13241 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13242 return m_Enabled;
13243 }
13244
13245 bool GetShutdown()
13246 {
13247 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13248 return m_Shutdown;
13249 }
13250
13251 void SetStatus(const std::string& status)
13252 {
13253 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13254 m_Status = status;
13255 #if DEBUG_UPLOADS
13256 debug_printf(">>> CUserReporterWorker status: %s\n", status.c_str());
13257 #endif
13258 }
13259
13260 bool ProcessReport()
13261 {
13262 PROFILE2("process report");
13263
13264 shared_ptr<CUserReport> report;
13265
13266 {
13267 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13268 if (m_ReportQueue.empty())
13269 return false;
13270 report = m_ReportQueue.front();
13271 m_ReportQueue.pop_front();
13272 }
13273
13274 ConstructRequestData(*report);
13275 m_RequestDataOffset = 0;
13276 m_ResponseData.clear();
13277 m_ErrorBuffer[0] = '\0';
13278
13279 curl_easy_setopt(m_Curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)m_RequestData.size());
13280
13281 SetStatus("connecting");
13282
13283 #if DEBUG_UPLOADS
13284 TIMER(L"CUserReporterWorker request");
13285 #endif
13286
13287 CURLcode err = curl_easy_perform(m_Curl);
13288
13289 #if DEBUG_UPLOADS
13290 printf(">>>\n%s\n<<<\n", m_ResponseData.c_str());
13291 #endif
13292
13293 if (err == CURLE_OK)
13294 {
13295 long code = -1;
13296 curl_easy_getinfo(m_Curl, CURLINFO_RESPONSE_CODE, &code);
13297 SetStatus("completed:" + CStr::FromInt(code));
13298
13299 // Check for success code
13300 if (code == 200)
13301 return true;
13302
13303 // If the server returns the 410 Gone status, interpret that as meaning
13304 // it no longer supports uploads (at least from this version of the game),
13305 // so shut down and stop talking to it (to avoid wasting bandwidth)
13306 if (code == 410)
13307 {
13308 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13309 m_Shutdown = true;
13310 return false;
13311 }
13312 }
13313 else
13314 {
13315 std::string errorString(m_ErrorBuffer);
13316
13317 if (errorString.empty())
13318 errorString = curl_easy_strerror(err);
13319
13320 SetStatus("failed:" + CStr::FromInt(err) + ":" + errorString);
13321 }
13322
13323 // We got an unhandled return code or a connection failure;
13324 // push this report back onto the queue and try again after
13325 // a long interval
13326
13327 {
13328 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13329 m_ReportQueue.push_front(report);
13330 }
13331
13332 m_PauseUntilTime = timer_Time() + RECONNECT_INVERVAL;
13333 return false;
13334 }
13335
13336 void ConstructRequestData(const CUserReport& report)
13337 {
13338 // Construct the POST request data in the application/x-www-form-urlencoded format
13339
13340 std::string r;
13341
13342 r += "user_id=";
13343 AppendEscaped(r, m_UserID);
13344
13345 r += "&time=" + CStr::FromInt64(report.m_Time);
13346
13347 r += "&type=";
13348 AppendEscaped(r, report.m_Type);
13349
13350 r += "&version=" + CStr::FromInt(report.m_Version);
13351
13352 r += "&data=";
13353 AppendEscaped(r, report.m_Data);
13354
13355 // Compress the content with zlib to save bandwidth.
13356 // (Note that we send a request with unlabelled compressed data instead
13357 // of using Content-Encoding, because Content-Encoding is a mess and causes
13358 // problems with servers and breaks Content-Length and this is much easier.)
13359 std::string compressed;
13360 compressed.resize(compressBound(r.size()));
13361 uLongf destLen = compressed.size();
13362 int ok = compress((Bytef*)compressed.c_str(), &destLen, (const Bytef*)r.c_str(), r.size());
13363 ENSURE(ok == Z_OK);
13364 compressed.resize(destLen);
13365
13366 m_RequestData.swap(compressed);
13367 }
13368
13369 void AppendEscaped(std::string& buffer, const std::string& str)
13370 {
13371 char* escaped = curl_easy_escape(m_Curl, str.c_str(), str.size());
13372 buffer += escaped;
13373 curl_free(escaped);
13374 }
13375
13376 static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp)
13377 {
13378 CUserReporterWorker* self = static_cast<CUserReporterWorker*>(userp);
13379
13380 if (self->GetShutdown())
13381 return 0; // signals an error
13382
13383 self->m_ResponseData += std::string((char*)buffer, (char*)buffer+size*nmemb);
13384
13385 return size*nmemb;
13386 }
13387
13388 static size_t SendCallback(char* bufptr, size_t size, size_t nmemb, void* userp)
13389 {
13390 CUserReporterWorker* self = static_cast<CUserReporterWorker*>(userp);
13391
13392 if (self->GetShutdown())
13393 return CURL_READFUNC_ABORT; // signals an error
13394
13395 // We can return as much data as available, up to the buffer size
13396 size_t amount = std::min(self->m_RequestData.size() - self->m_RequestDataOffset, size*nmemb);
13397
13398 // ...But restrict to sending a small amount at once, so that we remain
13399 // responsive to shutdown requests even if the network is pretty slow
13400 amount = std::min((size_t)1024, amount);
13401
13402 if(amount != 0) // (avoids invalid operator[] call where index=size)
13403 {
13404 memcpy(bufptr, &self->m_RequestData[self->m_RequestDataOffset], amount);
13405 self->m_RequestDataOffset += amount;
13406 }
13407
13408 self->SetStatus("sending:" + CStr::FromDouble((double)self->m_RequestDataOffset / self->m_RequestData.size()));
13409
13410 return amount;
13411 }
13412
13413 private:
13414 // Thread-related members:
13415 std::thread m_WorkerThread;
13416 std::mutex m_WorkerMutex;
13417 std::condition_variable m_WorkerCV;
13418
13419 // Shared by main thread and worker thread:
13420 // These variables are all protected by m_WorkerMutex
13421 std::deque<shared_ptr<CUserReport> > m_ReportQueue;
13422 bool m_Enabled;
13423 bool m_Shutdown;
13424 std::string m_Status;
13425
13426 // Initialised in constructor by main thread; otherwise used only by worker thread:
13427 std::string m_URL;
13428 std::string m_UserID;
13429 CURL* m_Curl;
13430 curl_slist* m_Headers;
13431 double m_PauseUntilTime;
13432
13433 // Only used by worker thread:
13434 std::string m_ResponseData;
13435 std::string m_RequestData;
13436 size_t m_RequestDataOffset;
13437 char m_ErrorBuffer[CURL_ERROR_SIZE];
13438
13439 // Only used by main thread:
13440 double m_LastUpdateTime;
13441 };
13442
13443
13444
13445 CUserReporter::CUserReporter() :
13446 m_Worker(NULL)
13447 {
13448 }
13449
13450 CUserReporter::~CUserReporter()
13451 {
13452 ENSURE(!m_Worker); // Deinitialize should have been called before shutdown
13453 }
13454
13455 std::string CUserReporter::LoadUserID()
13456 {
13457 std::string userID;
13458
13459 // Read the user ID from user.cfg (if there is one)
13460 CFG_GET_VAL("userreport.id", userID);
13461
13462 // If we don't have a validly-formatted user ID, generate a new one
13463 if (userID.length() != 16)
13464 {
13465 u8 bytes[8] = {0};
13466 sys_generate_random_bytes(bytes, ARRAY_SIZE(bytes));
13467 // ignore failures - there's not much we can do about it
13468
13469 userID = "";
13470 for (size_t i = 0; i < ARRAY_SIZE(bytes); ++i)
13471 {
13472 char hex[3];
13473 sprintf_s(hex, ARRAY_SIZE(hex), "%02x", (unsigned int)bytes[i]);
13474 userID += hex;
13475 }
13476
13477 g_ConfigDB.SetValueString(CFG_USER, "userreport.id", userID);
13478 g_ConfigDB.WriteValueToFile(CFG_USER, "userreport.id", userID);
13479 }
13480
13481 return userID;
13482 }
13483
13484 bool CUserReporter::IsReportingEnabled()
13485 {
13486 int version = -1;
13487 CFG_GET_VAL("userreport.enabledversion", version);
13488 return (version >= REPORTER_VERSION);
13489 }
13490
13491 void CUserReporter::SetReportingEnabled(bool enabled)
13492 {
13493 CStr val = CStr::FromInt(enabled ? REPORTER_VERSION : 0);
13494 g_ConfigDB.SetValueString(CFG_USER, "userreport.enabledversion", val);
13495 g_ConfigDB.WriteValueToFile(CFG_USER, "userreport.enabledversion", val);
13496
13497 if (m_Worker)
13498 m_Worker->SetEnabled(enabled);
13499 }
13500
13501 std::string CUserReporter::GetStatus()
13502 {
13503 if (!m_Worker)
13504 return "disabled";
13505
13506 return m_Worker->GetStatus();
13507 }
13508
13509 void CUserReporter::Initialize()
13510 {
13511 ENSURE(!m_Worker); // must only be called once
13512
13513 std::string userID = LoadUserID();
13514 std::string url;
13515 CFG_GET_VAL("userreport.url_upload", url);
13516
13517 m_Worker = new CUserReporterWorker(userID, url);
13518
13519 m_Worker->SetEnabled(IsReportingEnabled());
13520 }
13521
13522 void CUserReporter::Deinitialize()
13523 {
13524 if (!m_Worker)
13525 return;
13526
13527 if (m_Worker->Shutdown())
13528 {
13529 // Worker was shut down cleanly
13530 SAFE_DELETE(m_Worker);
13531 }
13532 else
13533 {
13534 // Worker failed to shut down in a reasonable time
13535 // Leak the resources (since that's better than hanging or crashing)
13536 m_Worker = NULL;
13537 }
13538 }
13539
13540 void CUserReporter::Update()
13541 {
13542 if (m_Worker)
13543 m_Worker->Update();
13544 }
13545
13546 void CUserReporter::SubmitReport(const std::string& type, int version, const std::string& data, const std::string& dataHumanReadable)
13547 {
13548 // Write to logfile, enabling users to assess privacy concerns before the data is submitted
13549 if (!dataHumanReadable.empty())
13550 {
13551 OsPath path = psLogDir() / OsPath("userreport_" + type + ".txt");
13552 std::ofstream stream(OsString(path), std::ofstream::trunc);
13553 if (stream)
13554 {
13555 debug_printf("UserReport written to %s\n", path.string8().c_str());
13556 stream << dataHumanReadable << std::endl;
13557 stream.close();
13558 }
13559 else
13560 debug_printf("Failed to write UserReport to %s\n", path.string8().c_str());
13561 }
13562
13563 // If not initialised, discard the report
13564 if (!m_Worker)
13565 return;
13566
13567 // Actual submit
13568 shared_ptr<CUserReport> report(new CUserReport);
13569 report->m_Time = time(NULL);
13570 report->m_Type = type;
13571 report->m_Version = version;
13572 report->m_Data = data;
13573
13574 m_Worker->Submit(report);
13575 }
13576Index: source/ps/Util.cpp
13577===================================================================
13578--- source/ps/Util.cpp (revision 23275)
13579+++ source/ps/Util.cpp (working copy)
13580@@ -1,451 +1,458 @@
13581 /* Copyright (C) 2019 Wildfire Games.
13582 * This file is part of 0 A.D.
13583 *
13584 * 0 A.D. is free software: you can redistribute it and/or modify
13585 * it under the terms of the GNU General Public License as published by
13586 * the Free Software Foundation, either version 2 of the License, or
13587 * (at your option) any later version.
13588 *
13589 * 0 A.D. is distributed in the hope that it will be useful,
13590 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13591 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13592 * GNU General Public License for more details.
13593 *
13594 * You should have received a copy of the GNU General Public License
13595 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
13596 */
13597
13598 #include "precompiled.h"
13599
13600 #include "ps/Util.h"
13601
13602 #include "lib/posix/posix_utsname.h"
13603 #include "lib/ogl.h"
13604 #include "lib/snd.h"
13605 #include "lib/timer.h"
13606 #include "lib/bits.h" // round_up
13607 #include "lib/allocators/shared_ptr.h"
13608 #include "lib/sysdep/sysdep.h" // sys_OpenFile
13609 #include "lib/sysdep/gfx.h"
13610 #include "lib/sysdep/cpu.h"
13611 #include "lib/sysdep/os_cpu.h"
13612 #if ARCH_X86_X64
13613 #include "lib/sysdep/arch/x86_x64/topology.h"
13614 #endif
13615 #include "lib/sysdep/smbios.h"
13616 #include "lib/tex/tex.h"
13617
13618 #include "i18n/L10n.h"
13619 #include "lib/utf8.h"
13620
13621 #include "ps/GameSetup/Config.h"
13622 #include "ps/GameSetup/GameSetup.h"
13623 #include "ps/Game.h"
13624 #include "ps/CLogger.h"
13625 #include "ps/Filesystem.h"
13626+#include "ps/Pyrogenesis.h"
13627 #include "ps/VideoMode.h"
13628 #include "renderer/Renderer.h"
13629 #include "maths/MathUtil.h"
13630 #include "graphics/GameView.h"
13631
13632 #include <iomanip>
13633 #include <sstream>
13634
13635 extern CStrW g_CursorName;
13636
13637 static std::string SplitExts(const char *exts)
13638 {
13639 std::string str = exts;
13640 std::string ret = "";
13641 size_t idx = str.find_first_of(" ");
13642 while(idx != std::string::npos)
13643 {
13644 if(idx >= str.length() - 1)
13645 {
13646 ret += str;
13647 break;
13648 }
13649
13650 ret += str.substr(0, idx);
13651 ret += "\n";
13652 str = str.substr(idx + 1);
13653 idx = str.find_first_of(" ");
13654 }
13655
13656 return ret;
13657 }
13658
13659
13660 void WriteSystemInfo()
13661 {
13662 TIMER(L"write_sys_info");
13663
13664+#if CONFIG2_AUDIO
13665 // get_cpu_info and gfx_detect already called during init - see call site
13666 snd_detect();
13667+#endif
13668
13669 struct utsname un;
13670 uname(&un);
13671
13672 OsPath pathname = psLogDir()/"system_info.txt";
13673 FILE* f = sys_OpenFile(pathname, "w");
13674 if(!f)
13675 return;
13676
13677 // current timestamp (redundant WRT OS timestamp, but that is not
13678 // visible when people are posting this file's contents online)
13679 {
13680 wchar_t timestampBuf[100] = {'\0'};
13681 time_t seconds;
13682 time(&seconds);
13683 struct tm* t = gmtime(&seconds);
13684 const size_t charsWritten = wcsftime(timestampBuf, ARRAY_SIZE(timestampBuf), L"(generated %Y-%m-%d %H:%M:%S UTC)", t);
13685 ENSURE(charsWritten != 0);
13686 fprintf(f, "%ls\n\n", timestampBuf);
13687 }
13688
13689 // OS
13690 fprintf(f, "OS : %s %s (%s)\n", un.sysname, un.release, un.version);
13691
13692 // CPU
13693 fprintf(f, "CPU : %s, %s", un.machine, cpu_IdentifierString());
13694 #if ARCH_X86_X64
13695 fprintf(f, " (%dx%dx%d)", (int)topology::NumPackages(), (int)topology::CoresPerPackage(), (int)topology::LogicalPerCore());
13696 #endif
13697 double cpuClock = os_cpu_ClockFrequency(); // query OS (may fail)
13698 #if ARCH_X86_X64
13699 if(cpuClock <= 0.0)
13700 cpuClock = x86_x64::ClockFrequency(); // measure (takes a few ms)
13701 #endif
13702 if(cpuClock > 0.0)
13703 {
13704 if(cpuClock < 1e9)
13705 fprintf(f, ", %.2f MHz\n", cpuClock*1e-6);
13706 else
13707 fprintf(f, ", %.2f GHz\n", cpuClock*1e-9);
13708 }
13709 else
13710 fprintf(f, "\n");
13711
13712 // memory
13713 fprintf(f, "Memory : %u MiB; %u MiB free\n", (unsigned)os_cpu_MemorySize(), (unsigned)os_cpu_MemoryAvailable());
13714
13715 // graphics
13716 const std::wstring cardName = gfx::CardName();
13717 const std::wstring driverInfo = gfx::DriverInfo();
13718 fprintf(f, "Graphics Card : %ls\n", cardName.c_str());
13719 fprintf(f, "OpenGL Drivers : %s; %ls\n", glGetString(GL_VERSION), driverInfo.c_str());
13720 fprintf(f, "Video Mode : %dx%d:%d\n", g_VideoMode.GetXRes(), g_VideoMode.GetYRes(), g_VideoMode.GetBPP());
13721
13722 // sound
13723+#if CONFIG2_AUDIO
13724 fprintf(f, "Sound Card : %s\n", snd_card.c_str());
13725 fprintf(f, "Sound Drivers : %s\n", snd_drv_ver.c_str());
13726+#else
13727+ fprintf(f, "Sound : disabled\n");
13728+#endif
13729
13730 // OpenGL extensions (write them last, since it's a lot of text)
13731 const char* exts = ogl_ExtensionString();
13732 if (!exts) exts = "{unknown}";
13733 fprintf(f, "\nOpenGL Extensions: \n%s\n", SplitExts(exts).c_str());
13734
13735 // System Management BIOS (even more text than OpenGL extensions)
13736 std::string smbios = SMBIOS::StringizeStructures(SMBIOS::GetStructures());
13737 fprintf(f, "\nSMBIOS: \n%s\n", smbios.c_str());
13738
13739 fclose(f);
13740 f = 0;
13741 }
13742
13743
13744 // not thread-safe!
13745 static const wchar_t* HardcodedErrorString(int err)
13746 {
13747 static wchar_t description[200];
13748 StatusDescription((Status)err, description, ARRAY_SIZE(description));
13749 return description;
13750 }
13751
13752 // not thread-safe!
13753 const wchar_t* ErrorString(int err)
13754 {
13755 // language file not available (yet)
13756 return HardcodedErrorString(err);
13757
13758 // TODO: load from language file
13759 }
13760
13761
13762
13763 // write the specified texture to disk.
13764 // note: <t> cannot be made const because the image may have to be
13765 // transformed to write it out in the format determined by <fn>'s extension.
13766 Status tex_write(Tex* t, const VfsPath& filename)
13767 {
13768 DynArray da;
13769 RETURN_STATUS_IF_ERR(t->encode(filename.Extension(), &da));
13770
13771 // write to disk
13772 Status ret = INFO::OK;
13773 {
13774 shared_ptr<u8> file = DummySharedPtr(da.base);
13775 const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
13776 if(bytes_written > 0)
13777 ENSURE(bytes_written == (ssize_t)da.pos);
13778 else
13779 ret = (Status)bytes_written;
13780 }
13781
13782 (void)da_free(&da);
13783 return ret;
13784 }
13785
13786 /**
13787 * Return an unused directory, based on date and index (for example 2016-02-09_0001)
13788 */
13789 OsPath createDateIndexSubdirectory(const OsPath& parentDir)
13790 {
13791 const std::time_t timestamp = std::time(nullptr);
13792 const struct std::tm* now = std::localtime(×tamp);
13793
13794 // Two processes executing this simultaneously might attempt to create the same directory.
13795 int tries = 0;
13796 const int maxTries = 10;
13797
13798 int i = 0;
13799 OsPath path;
13800 char directory[256];
13801
13802 do
13803 {
13804 sprintf(directory, "%04d-%02d-%02d_%04d", now->tm_year+1900, now->tm_mon+1, now->tm_mday, ++i);
13805 path = parentDir / CStr(directory);
13806
13807 if (DirectoryExists(path) || FileExists(path))
13808 continue;
13809
13810 if (CreateDirectories(path, 0700, ++tries > maxTries) == INFO::OK)
13811 break;
13812
13813 } while(tries <= maxTries);
13814
13815 return path;
13816 }
13817
13818 static size_t s_nextScreenshotNumber;
13819
13820 // <extension> identifies the file format that is to be written
13821 // (case-insensitive). examples: "bmp", "png", "jpg".
13822 // BMP is good for quick output at the expense of large files.
13823 void WriteScreenshot(const VfsPath& extension)
13824 {
13825 // get next available numbered filename
13826 // note: %04d -> always 4 digits, so sorting by filename works correctly.
13827 const VfsPath basenameFormat(L"screenshots/screenshot%04d");
13828 const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension);
13829 VfsPath filename;
13830 vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename);
13831
13832 const size_t w = (size_t)g_xres, h = (size_t)g_yres;
13833 const size_t bpp = 24;
13834 GLenum fmt = GL_RGB;
13835 int flags = TEX_BOTTOM_UP;
13836 // we want writing BMP to be as fast as possible,
13837 // so read data from OpenGL in BMP format to obviate conversion.
13838 if(extension == L".bmp")
13839 {
13840 #if !CONFIG2_GLES // GLES doesn't support BGR
13841 fmt = GL_BGR;
13842 flags |= TEX_BGR;
13843 #endif
13844 }
13845
13846 // Hide log messages and re-render
13847 RenderLogger(false);
13848 Render();
13849 RenderLogger(true);
13850
13851 const size_t img_size = w * h * bpp/8;
13852 const size_t hdr_size = tex_hdr_size(filename);
13853 shared_ptr<u8> buf;
13854 AllocateAligned(buf, hdr_size+img_size, maxSectorSize);
13855 GLvoid* img = buf.get() + hdr_size;
13856 Tex t;
13857 if(t.wrap(w, h, bpp, flags, buf, hdr_size) < 0)
13858 return;
13859 glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img);
13860
13861 if (tex_write(&t, filename) == INFO::OK)
13862 {
13863 OsPath realPath;
13864 g_VFS->GetRealPath(filename, realPath);
13865
13866 LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
13867
13868 debug_printf(
13869 CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
13870 realPath.string8().c_str());
13871 }
13872 else
13873 LOGERROR("Error writing screenshot to '%s'", filename.string8());
13874 }
13875
13876
13877
13878 // Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles.
13879 void WriteBigScreenshot(const VfsPath& extension, int tiles)
13880 {
13881 // If the game hasn't started yet then use WriteScreenshot to generate the image.
13882 if(g_Game == NULL){ WriteScreenshot(L".bmp"); return; }
13883
13884 // get next available numbered filename
13885 // note: %04d -> always 4 digits, so sorting by filename works correctly.
13886 const VfsPath basenameFormat(L"screenshots/screenshot%04d");
13887 const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension);
13888 VfsPath filename;
13889 vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename);
13890
13891 // Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and
13892 // hope the screen is actually large enough for that.
13893 const int tile_w = 640, tile_h = 480;
13894 ENSURE(g_xres >= tile_w && g_yres >= tile_h);
13895
13896 const int img_w = tile_w*tiles, img_h = tile_h*tiles;
13897 const int bpp = 24;
13898 GLenum fmt = GL_RGB;
13899 int flags = TEX_BOTTOM_UP;
13900 // we want writing BMP to be as fast as possible,
13901 // so read data from OpenGL in BMP format to obviate conversion.
13902 if(extension == L".bmp")
13903 {
13904 #if !CONFIG2_GLES // GLES doesn't support BGR
13905 fmt = GL_BGR;
13906 flags |= TEX_BGR;
13907 #endif
13908 }
13909
13910 const size_t img_size = img_w * img_h * bpp/8;
13911 const size_t tile_size = tile_w * tile_h * bpp/8;
13912 const size_t hdr_size = tex_hdr_size(filename);
13913 void* tile_data = malloc(tile_size);
13914 if(!tile_data)
13915 {
13916 WARN_IF_ERR(ERR::NO_MEM);
13917 return;
13918 }
13919 shared_ptr<u8> img_buf;
13920 AllocateAligned(img_buf, hdr_size+img_size, maxSectorSize);
13921
13922 Tex t;
13923 GLvoid* img = img_buf.get() + hdr_size;
13924 if(t.wrap(img_w, img_h, bpp, flags, img_buf, hdr_size) < 0)
13925 {
13926 free(tile_data);
13927 return;
13928 }
13929
13930 ogl_WarnIfError();
13931
13932 CCamera oldCamera = *g_Game->GetView()->GetCamera();
13933
13934 // Resize various things so that the sizes and aspect ratios are correct
13935 {
13936 g_Renderer.Resize(tile_w, tile_h);
13937 SViewPort vp = { 0, 0, tile_w, tile_h };
13938 g_Game->GetView()->SetViewport(vp);
13939 }
13940
13941 #if !CONFIG2_GLES
13942 // Temporarily move everything onto the front buffer, so the user can
13943 // see the exciting progress as it renders (and can tell when it's finished).
13944 // (It doesn't just use SwapBuffers, because it doesn't know whether to
13945 // call the SDL version or the Atlas version.)
13946 GLint oldReadBuffer, oldDrawBuffer;
13947 glGetIntegerv(GL_READ_BUFFER, &oldReadBuffer);
13948 glGetIntegerv(GL_DRAW_BUFFER, &oldDrawBuffer);
13949 glDrawBuffer(GL_FRONT);
13950 glReadBuffer(GL_FRONT);
13951 #endif
13952
13953 // Hide the cursor
13954 CStrW oldCursor = g_CursorName;
13955 g_CursorName = L"";
13956
13957 // Render each tile
13958 CMatrix3D projection;
13959 projection.SetIdentity();
13960 for (int tile_y = 0; tile_y < tiles; ++tile_y)
13961 {
13962 for (int tile_x = 0; tile_x < tiles; ++tile_x)
13963 {
13964 // Adjust the camera to render the appropriate region
13965 if (oldCamera.GetProjectionType() == CCamera::PERSPECTIVE)
13966 {
13967 projection.SetPerspectiveTile(oldCamera.GetFOV(), oldCamera.GetAspectRatio(), oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tile_x, tile_y);
13968 }
13969 g_Game->GetView()->GetCamera()->SetProjection(projection);
13970
13971 RenderLogger(false);
13972 RenderGui(false);
13973 Render();
13974 RenderGui(true);
13975 RenderLogger(true);
13976
13977 // Copy the tile pixels into the main image
13978 glReadPixels(0, 0, tile_w, tile_h, fmt, GL_UNSIGNED_BYTE, tile_data);
13979 for (int y = 0; y < tile_h; ++y)
13980 {
13981 void* dest = (char*)img + ((tile_y*tile_h + y) * img_w + (tile_x*tile_w)) * bpp/8;
13982 void* src = (char*)tile_data + y * tile_w * bpp/8;
13983 memcpy(dest, src, tile_w * bpp/8);
13984 }
13985 }
13986 }
13987
13988 // Restore the old cursor
13989 g_CursorName = oldCursor;
13990
13991 #if !CONFIG2_GLES
13992 // Restore the buffer settings
13993 glDrawBuffer(oldDrawBuffer);
13994 glReadBuffer(oldReadBuffer);
13995 #endif
13996
13997 // Restore the viewport settings
13998 {
13999 g_Renderer.Resize(g_xres, g_yres);
14000 SViewPort vp = { 0, 0, g_xres, g_yres };
14001 g_Game->GetView()->SetViewport(vp);
14002 g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera);
14003 }
14004
14005 if (tex_write(&t, filename) == INFO::OK)
14006 {
14007 OsPath realPath;
14008 g_VFS->GetRealPath(filename, realPath);
14009
14010 LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
14011
14012 debug_printf(
14013 CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
14014 realPath.string8().c_str());
14015 }
14016 else
14017 LOGERROR("Error writing screenshot to '%s'", filename.string8());
14018
14019 free(tile_data);
14020 }
14021
14022 std::string Hexify(const std::string& s)
14023 {
14024 std::stringstream str;
14025 str << std::hex;
14026 for (const char& c : s)
14027 str << std::setfill('0') << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c));
14028 return str.str();
14029 }
14030
14031 std::string Hexify(const u8* s, size_t length)
14032 {
14033 std::stringstream str;
14034 str << std::hex;
14035 for (size_t i = 0; i < length; ++i)
14036 str << std::setfill('0') << std::setw(2) << static_cast<int>(s[i]);
14037 return str.str();
14038 }
14039Index: source/ps/VideoMode.cpp
14040===================================================================
14041--- source/ps/VideoMode.cpp (revision 23275)
14042+++ source/ps/VideoMode.cpp (working copy)
14043@@ -1,535 +1,544 @@
14044 /* Copyright (C) 2018 Wildfire Games.
14045 * This file is part of 0 A.D.
14046 *
14047 * 0 A.D. is free software: you can redistribute it and/or modify
14048 * it under the terms of the GNU General Public License as published by
14049 * the Free Software Foundation, either version 2 of the License, or
14050 * (at your option) any later version.
14051 *
14052 * 0 A.D. is distributed in the hope that it will be useful,
14053 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14054 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14055 * GNU General Public License for more details.
14056 *
14057 * You should have received a copy of the GNU General Public License
14058 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
14059 */
14060
14061 #include "precompiled.h"
14062
14063 #include "VideoMode.h"
14064
14065 #include "graphics/Camera.h"
14066 #include "graphics/GameView.h"
14067 #include "gui/GUIManager.h"
14068 #include "lib/config2.h"
14069 #include "lib/ogl.h"
14070 #include "lib/external_libraries/libsdl.h"
14071 #include "lib/sysdep/gfx.h"
14072 #include "lib/tex/tex.h"
14073 #include "ps/CConsole.h"
14074 #include "ps/CLogger.h"
14075 #include "ps/ConfigDB.h"
14076 #include "ps/Filesystem.h"
14077 #include "ps/Game.h"
14078 #include "ps/GameSetup/Config.h"
14079 #include "renderer/Renderer.h"
14080
14081 #if OS_MACOSX
14082 # include "lib/sysdep/os/osx/osx_sys_version.h"
14083 #endif
14084
14085
14086 static int DEFAULT_WINDOW_W = 1024;
14087 static int DEFAULT_WINDOW_H = 768;
14088
14089 static int DEFAULT_FULLSCREEN_W = 1024;
14090 static int DEFAULT_FULLSCREEN_H = 768;
14091
14092 CVideoMode g_VideoMode;
14093
14094 CVideoMode::CVideoMode() :
14095 m_IsFullscreen(false), m_IsInitialised(false), m_Window(NULL),
14096 m_PreferredW(0), m_PreferredH(0), m_PreferredBPP(0), m_PreferredFreq(0),
14097 m_ConfigW(0), m_ConfigH(0), m_ConfigBPP(0), m_ConfigFullscreen(false), m_ConfigForceS3TCEnable(true),
14098 m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0)
14099 {
14100 // (m_ConfigFullscreen defaults to false, so users don't get stuck if
14101 // e.g. half the filesystem is missing and the config files aren't loaded)
14102 }
14103
14104 void CVideoMode::ReadConfig()
14105 {
14106 bool windowed = !m_ConfigFullscreen;
14107 CFG_GET_VAL("windowed", windowed);
14108 m_ConfigFullscreen = !windowed;
14109
14110 CFG_GET_VAL("xres", m_ConfigW);
14111 CFG_GET_VAL("yres", m_ConfigH);
14112 CFG_GET_VAL("bpp", m_ConfigBPP);
14113 CFG_GET_VAL("display", m_ConfigDisplay);
14114 CFG_GET_VAL("force_s3tc_enable", m_ConfigForceS3TCEnable);
14115 }
14116
14117 bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
14118 {
14119 Uint32 flags = 0;
14120 if (fullscreen)
14121 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
14122
14123 if (!m_Window)
14124 {
14125 // Note: these flags only take affect in SDL_CreateWindow
14126 flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
14127 m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay);
14128
14129 m_Window = SDL_CreateWindow("0 A.D.", m_WindowedX, m_WindowedY, w, h, flags);
14130 if (!m_Window)
14131 {
14132 // If fullscreen fails, try windowed mode
14133 if (fullscreen)
14134 {
14135 LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution "
14136 "%dx%d:%d (\"%hs\"), falling back to windowed mode",
14137 w, h, bpp, SDL_GetError());
14138 // Using default size for the window for now, as the attempted setting
14139 // could be as large, or larger than the screen size.
14140 return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false);
14141 }
14142 else
14143 {
14144 LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")",
14145 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
14146 return false;
14147 }
14148 }
14149
14150 if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0)
14151 {
14152 LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")",
14153 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
14154 return false;
14155 }
14156
14157 SDL_GLContext context = SDL_GL_CreateContext(m_Window);
14158 if (!context)
14159 {
14160 LOGERROR("SetVideoMode failed in SDL_GL_CreateContext: %dx%d:%d %d (\"%s\")",
14161 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
14162 return false;
14163 }
14164 }
14165 else
14166 {
14167 if (m_IsFullscreen != fullscreen)
14168 {
14169 if (!fullscreen)
14170 {
14171 // For some reason, when switching from fullscreen to windowed mode,
14172 // we have to set the window size and position before and after switching
14173 SDL_SetWindowSize(m_Window, w, h);
14174 SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
14175 }
14176
14177 if (SDL_SetWindowFullscreen(m_Window, flags) < 0)
14178 {
14179 LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")",
14180 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
14181 return false;
14182 }
14183 }
14184
14185 if (!fullscreen)
14186 {
14187 SDL_SetWindowSize(m_Window, w, h);
14188 SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
14189 }
14190 }
14191
14192 // Grab the current video settings
14193 SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH);
14194 m_CurrentBPP = bpp;
14195
14196 if (fullscreen)
14197 SDL_SetWindowGrab(m_Window, SDL_TRUE);
14198 else
14199 SDL_SetWindowGrab(m_Window, SDL_FALSE);
14200
14201 m_IsFullscreen = fullscreen;
14202
14203 g_xres = m_CurrentW;
14204 g_yres = m_CurrentH;
14205
14206 return true;
14207 }
14208
14209 bool CVideoMode::InitSDL()
14210 {
14211+ SDL_DisplayMode mode;
14212+
14213 ENSURE(!m_IsInitialised);
14214
14215 ReadConfig();
14216
14217 EnableS3TC();
14218
14219 // preferred video mode = current desktop settings
14220 // (command line params may override these)
14221- gfx::GetVideoMode(&m_PreferredW, &m_PreferredH, &m_PreferredBPP, &m_PreferredFreq);
14222+ // TODO: handle multi-screen properly
14223+ if (SDL_GetDesktopDisplayMode(0, &mode) == 0)
14224+ {
14225+ m_PreferredW = mode.w;
14226+ m_PreferredH = mode.h;
14227+ m_PreferredBPP = SDL_BITSPERPIXEL(mode.format);
14228+ m_PreferredFreq = mode.refresh_rate;
14229+ }
14230
14231 int w = m_ConfigW;
14232 int h = m_ConfigH;
14233
14234 if (m_ConfigFullscreen)
14235 {
14236 // If fullscreen and no explicit size set, default to the desktop resolution
14237 if (w == 0 || h == 0)
14238 {
14239 w = m_PreferredW;
14240 h = m_PreferredH;
14241 }
14242 }
14243
14244 // If no size determined, default to something sensible
14245 if (w == 0 || h == 0)
14246 {
14247 w = DEFAULT_WINDOW_W;
14248 h = DEFAULT_WINDOW_H;
14249 }
14250
14251 if (!m_ConfigFullscreen)
14252 {
14253 // Limit the window to the screen size (if known)
14254 if (m_PreferredW)
14255 w = std::min(w, m_PreferredW);
14256 if (m_PreferredH)
14257 h = std::min(h, m_PreferredH);
14258 }
14259
14260 int bpp = GetBestBPP();
14261
14262 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
14263 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
14264 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
14265
14266 #if CONFIG2_GLES
14267 // Require GLES 2.0
14268 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
14269 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
14270 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
14271 #endif
14272
14273 if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
14274 {
14275 // Fall back to a smaller depth buffer
14276 // (The rendering may be ugly but this helps when running in VMware)
14277 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
14278
14279 if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
14280 return false;
14281 }
14282
14283 SDL_GL_SetSwapInterval(g_VSync ? 1 : 0);
14284
14285 // Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13).
14286 // The driver appears to register its own atexit hook on context creation.
14287 // If this atexit hook is called before SDL_Quit destroys the OpenGL context,
14288 // some kind of double-free problem causes a crash and lockup in the driver.
14289 // Calling SDL_Quit twice appears to be harmless, though, and avoids the problem
14290 // by destroying the context *before* the driver's atexit hook is called.
14291 // (Note that atexit hooks are guaranteed to be called in reverse order of their registration.)
14292 atexit(SDL_Quit);
14293 // End work around.
14294
14295 ogl_Init(); // required after each mode change
14296 // (TODO: does that mean we need to call this when toggling fullscreen later?)
14297
14298 #if !OS_ANDROID
14299 u16 ramp[256];
14300 SDL_CalculateGammaRamp(g_Gamma, ramp);
14301 if (SDL_SetWindowGammaRamp(m_Window, ramp, ramp, ramp) < 0)
14302 LOGWARNING("SDL_SetWindowGammaRamp failed");
14303 #endif
14304
14305 m_IsInitialised = true;
14306
14307 if (!m_ConfigFullscreen)
14308 {
14309 m_WindowedW = w;
14310 m_WindowedH = h;
14311 }
14312
14313 SetWindowIcon();
14314
14315 return true;
14316 }
14317
14318 bool CVideoMode::InitNonSDL()
14319 {
14320 ENSURE(!m_IsInitialised);
14321
14322 ReadConfig();
14323
14324 EnableS3TC();
14325
14326 m_IsInitialised = true;
14327
14328 return true;
14329 }
14330
14331 void CVideoMode::Shutdown()
14332 {
14333 ENSURE(m_IsInitialised);
14334
14335 m_IsFullscreen = false;
14336 m_IsInitialised = false;
14337 if (m_Window)
14338 {
14339 SDL_DestroyWindow(m_Window);
14340 m_Window = NULL;
14341 }
14342 }
14343
14344 void CVideoMode::EnableS3TC()
14345 {
14346 // On Linux we have to try hard to get S3TC compressed texture support.
14347 // If the extension is already provided by default, that's fine.
14348 // Otherwise we should enable the 'force_s3tc_enable' environment variable
14349 // and (re)initialise the video system, so that Mesa provides the extension
14350 // (if the driver at least supports decompression).
14351 // (This overrides the force_s3tc_enable specified via driconf files.)
14352 // Otherwise we should complain to the user, and stop using compressed textures.
14353 //
14354 // Setting the environment variable causes Mesa to print an ugly message to stderr
14355 // ("ATTENTION: default value of option force_s3tc_enable overridden by environment."),
14356 // so it'd be nicer to skip that if S3TC will be supported by default,
14357 // but reinitialising video is a pain (and it might do weird things when fullscreen)
14358 // so we just unconditionally set it (unless our config file explicitly disables it).
14359
14360 #if !(OS_WIN || OS_MACOSX) // (assume Mesa is used for all non-Windows non-Mac platforms)
14361 if (m_ConfigForceS3TCEnable)
14362 setenv("force_s3tc_enable", "true", 0);
14363 #endif
14364 }
14365
14366 bool CVideoMode::ResizeWindow(int w, int h)
14367 {
14368 ENSURE(m_IsInitialised);
14369
14370 // Ignore if not windowed
14371 if (m_IsFullscreen)
14372 return true;
14373
14374 // Ignore if the size hasn't changed
14375 if (w == m_WindowedW && h == m_WindowedH)
14376 return true;
14377
14378 int bpp = GetBestBPP();
14379
14380 if (!SetVideoMode(w, h, bpp, false))
14381 return false;
14382
14383 m_WindowedW = w;
14384 m_WindowedH = h;
14385
14386 UpdateRenderer(w, h);
14387
14388 return true;
14389 }
14390
14391 bool CVideoMode::SetFullscreen(bool fullscreen)
14392 {
14393 // This might get called before initialisation by psDisplayError;
14394 // if so then silently fail
14395 if (!m_IsInitialised)
14396 return false;
14397
14398 // Check whether this is actually a change
14399 if (fullscreen == m_IsFullscreen)
14400 return true;
14401
14402 if (!m_IsFullscreen)
14403 {
14404 // Windowed -> fullscreen:
14405
14406 int w = 0, h = 0;
14407
14408 // If a fullscreen size was configured, use that; else use the desktop size; else use a default
14409 if (m_ConfigFullscreen)
14410 {
14411 w = m_ConfigW;
14412 h = m_ConfigH;
14413 }
14414 if (w == 0 || h == 0)
14415 {
14416 w = m_PreferredW;
14417 h = m_PreferredH;
14418 }
14419 if (w == 0 || h == 0)
14420 {
14421 w = DEFAULT_FULLSCREEN_W;
14422 h = DEFAULT_FULLSCREEN_H;
14423 }
14424
14425 int bpp = GetBestBPP();
14426
14427 if (!SetVideoMode(w, h, bpp, fullscreen))
14428 return false;
14429
14430 UpdateRenderer(m_CurrentW, m_CurrentH);
14431
14432 return true;
14433 }
14434 else
14435 {
14436 // Fullscreen -> windowed:
14437
14438 // Go back to whatever the previous window size was
14439 int w = m_WindowedW, h = m_WindowedH;
14440
14441 int bpp = GetBestBPP();
14442
14443 if (!SetVideoMode(w, h, bpp, fullscreen))
14444 return false;
14445
14446 UpdateRenderer(w, h);
14447
14448 return true;
14449 }
14450 }
14451
14452 bool CVideoMode::ToggleFullscreen()
14453 {
14454 return SetFullscreen(!m_IsFullscreen);
14455 }
14456
14457 bool CVideoMode::IsInFullscreen() const
14458 {
14459 return m_IsFullscreen;
14460 }
14461
14462 void CVideoMode::UpdatePosition(int x, int y)
14463 {
14464 if (!m_IsFullscreen)
14465 {
14466 m_WindowedX = x;
14467 m_WindowedY = y;
14468 }
14469 }
14470
14471 void CVideoMode::UpdateRenderer(int w, int h)
14472 {
14473 if (w < 2) w = 2; // avoid GL errors caused by invalid sizes
14474 if (h < 2) h = 2;
14475
14476 g_xres = w;
14477 g_yres = h;
14478
14479 SViewPort vp = { 0, 0, w, h };
14480
14481 if (CRenderer::IsInitialised())
14482 {
14483 g_Renderer.SetViewport(vp);
14484 g_Renderer.Resize(w, h);
14485 }
14486
14487 if (g_GUI)
14488 g_GUI->UpdateResolution();
14489
14490 if (g_Console)
14491 g_Console->UpdateScreenSize(w, h);
14492
14493 if (g_Game)
14494 g_Game->GetView()->SetViewport(vp);
14495 }
14496
14497 int CVideoMode::GetBestBPP()
14498 {
14499 if (m_ConfigBPP)
14500 return m_ConfigBPP;
14501 if (m_PreferredBPP)
14502 return m_PreferredBPP;
14503 return 32;
14504 }
14505
14506 int CVideoMode::GetXRes()
14507 {
14508 ENSURE(m_IsInitialised);
14509 return m_CurrentW;
14510 }
14511
14512 int CVideoMode::GetYRes()
14513 {
14514 ENSURE(m_IsInitialised);
14515 return m_CurrentH;
14516 }
14517
14518 int CVideoMode::GetBPP()
14519 {
14520 ENSURE(m_IsInitialised);
14521 return m_CurrentBPP;
14522 }
14523
14524 int CVideoMode::GetDesktopXRes()
14525 {
14526 ENSURE(m_IsInitialised);
14527 return m_PreferredW;
14528 }
14529
14530 int CVideoMode::GetDesktopYRes()
14531 {
14532 ENSURE(m_IsInitialised);
14533 return m_PreferredH;
14534 }
14535
14536 int CVideoMode::GetDesktopBPP()
14537 {
14538 ENSURE(m_IsInitialised);
14539 return m_PreferredBPP;
14540 }
14541
14542 int CVideoMode::GetDesktopFreq()
14543 {
14544 ENSURE(m_IsInitialised);
14545 return m_PreferredFreq;
14546 }
14547
14548 SDL_Window* CVideoMode::GetWindow()
14549 {
14550 ENSURE(m_IsInitialised);
14551 return m_Window;
14552 }
14553
14554 void CVideoMode::SetWindowIcon()
14555 {
14556 // The window icon should be kept outside of art/textures/, or else it will be converted
14557 // to DDS by the archive builder and will become unusable here. Using DDS makes BGRA
14558 // conversion needlessly complicated.
14559 std::shared_ptr<u8> iconFile;
14560 size_t iconFileSize;
14561 if (g_VFS->LoadFile("art/icons/window.png", iconFile, iconFileSize) != INFO::OK)
14562 {
14563 LOGWARNING("Window icon not found.");
14564 return;
14565 }
14566
14567 Tex iconTexture;
14568 if (iconTexture.decode(iconFile, iconFileSize) != INFO::OK)
14569 return;
14570
14571 // Convert to required BGRA format.
14572 const size_t iconFlags = (iconTexture.m_Flags | TEX_BGR) & ~TEX_DXT;
14573 if (iconTexture.transform_to(iconFlags) != INFO::OK)
14574 return;
14575
14576 void* bgra_img = iconTexture.get_data();
14577 if (!bgra_img)
14578 return;
14579
14580 SDL_Surface *iconSurface = SDL_CreateRGBSurfaceFrom(bgra_img,
14581 iconTexture.m_Width, iconTexture.m_Height, 32, iconTexture.m_Width * 4,
14582 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
14583 if (!iconSurface)
14584 return;
14585
14586 SDL_SetWindowIcon(m_Window, iconSurface);
14587 SDL_FreeSurface(iconSurface);
14588 }
14589Index: source/ps/VideoMode.h
14590===================================================================
14591--- source/ps/VideoMode.h (revision 23275)
14592+++ source/ps/VideoMode.h (working copy)
14593@@ -1,137 +1,138 @@
14594 /* Copyright (C) 2018 Wildfire Games.
14595 * This file is part of 0 A.D.
14596 *
14597 * 0 A.D. is free software: you can redistribute it and/or modify
14598 * it under the terms of the GNU General Public License as published by
14599 * the Free Software Foundation, either version 2 of the License, or
14600 * (at your option) any later version.
14601 *
14602 * 0 A.D. is distributed in the hope that it will be useful,
14603 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14604 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14605 * GNU General Public License for more details.
14606 *
14607 * You should have received a copy of the GNU General Public License
14608 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
14609 */
14610
14611 #ifndef INCLUDED_VIDEOMODE
14612 #define INCLUDED_VIDEOMODE
14613
14614 typedef struct SDL_Window SDL_Window;
14615
14616 class CVideoMode
14617 {
14618 public:
14619 CVideoMode();
14620
14621 /**
14622 * Initialise the video mode, for use in an SDL-using application.
14623 */
14624 bool InitSDL();
14625
14626 /**
14627 * Initialise parts of the video mode, for use in Atlas (which uses
14628 * wxWidgets instead of SDL for GL).
14629 * Currently this just tries to enable S3TC.
14630 */
14631 bool InitNonSDL();
14632
14633 /**
14634 * Shut down after InitSDL/InitNonSDL, so that they can be used again.
14635 */
14636 void Shutdown();
14637
14638 /**
14639 * Resize the SDL window and associated graphics stuff to the new size.
14640 */
14641 bool ResizeWindow(int w, int h);
14642
14643 /**
14644 * Switch to fullscreen or windowed mode.
14645 */
14646 bool SetFullscreen(bool fullscreen);
14647
14648 /**
14649 * Returns true if window runs in fullscreen mode.
14650 */
14651 bool IsInFullscreen() const;
14652
14653 /**
14654 * Switch between fullscreen and windowed mode.
14655 */
14656 bool ToggleFullscreen();
14657
14658 /**
14659 * Update window position, to restore later if necessary (SDL2 only).
14660 */
14661 void UpdatePosition(int x, int y);
14662
14663 /**
14664 * Update the graphics code to start drawing to the new size.
14665 * This should be called after the GL context has been resized.
14666 * This can also be used when the GL context is managed externally, not via SDL.
14667 */
14668 static void UpdateRenderer(int w, int h);
14669
14670 int GetXRes();
14671 int GetYRes();
14672 int GetBPP();
14673
14674 int GetDesktopXRes();
14675 int GetDesktopYRes();
14676 int GetDesktopBPP();
14677 int GetDesktopFreq();
14678
14679 SDL_Window* GetWindow();
14680
14681 void SetWindowIcon();
14682
14683 private:
14684 void ReadConfig();
14685 int GetBestBPP();
14686 bool SetVideoMode(int w, int h, int bpp, bool fullscreen);
14687 void EnableS3TC();
14688
14689 /**
14690 * Remember whether Init has been called. (This isn't used for anything
14691 * important, just for verifying that the callers call our methods in
14692 * the right order.)
14693 */
14694 bool m_IsInitialised;
14695
14696 SDL_Window* m_Window;
14697
14698- // Initial desktop settings
14699+ // Initial desktop settings.
14700+ // Frequency is in Hz, and BPP actually means bits per pixels (not bytes per pixels)
14701 int m_PreferredW;
14702 int m_PreferredH;
14703 int m_PreferredBPP;
14704 int m_PreferredFreq;
14705
14706 // Config file settings (0 if unspecified)
14707 int m_ConfigW;
14708 int m_ConfigH;
14709 int m_ConfigBPP;
14710 int m_ConfigDisplay;
14711 bool m_ConfigFullscreen;
14712 bool m_ConfigForceS3TCEnable;
14713
14714 // If we're fullscreen, size/position of window when we were last windowed (or the default window
14715 // size/position if we started fullscreen), to support switching back to the old window size/position
14716 int m_WindowedW;
14717 int m_WindowedH;
14718 int m_WindowedX;
14719 int m_WindowedY;
14720
14721 // Whether we're currently being displayed fullscreen
14722 bool m_IsFullscreen;
14723
14724 // The last mode selected
14725 int m_CurrentW;
14726 int m_CurrentH;
14727 int m_CurrentBPP;
14728 };
14729
14730 extern CVideoMode g_VideoMode;
14731
14732 #endif // INCLUDED_VIDEOMODE
14733Index: source/ps/VisualReplay.h
14734===================================================================
14735--- source/ps/VisualReplay.h (revision 23275)
14736+++ source/ps/VisualReplay.h (working copy)
14737@@ -1,123 +1,125 @@
14738 /* Copyright (C) 2019 Wildfire Games.
14739 * This file is part of 0 A.D.
14740 *
14741 * 0 A.D. is free software: you can redistribute it and/or modify
14742 * it under the terms of the GNU General Public License as published by
14743 * the Free Software Foundation, either version 2 of the License, or
14744 * (at your option) any later version.
14745 *
14746 * 0 A.D. is distributed in the hope that it will be useful,
14747 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14748 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14749 * GNU General Public License for more details.
14750 *
14751 * You should have received a copy of the GNU General Public License
14752 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
14753 */
14754
14755 #ifndef INCLUDED_REPlAY
14756 #define INCLUDED_REPlAY
14757
14758 #include "scriptinterface/ScriptInterface.h"
14759+#include "lib/os_path.h"
14760+
14761 class CSimulation2;
14762 class CGUIManager;
14763
14764 /**
14765 * Contains functions for visually replaying past games.
14766 */
14767 namespace VisualReplay
14768 {
14769
14770 /**
14771 * Returns the absolute path to the sim-log directory (that contains the directories with the replay files.
14772 */
14773 OsPath GetDirectoryPath();
14774
14775 /**
14776 * Returns the absolute path to the replay cache file.
14777 */
14778 OsPath GetCacheFilePath();
14779
14780 /**
14781 * Returns the absolute path to the temporary replay cache file used to
14782 * always have a valid cache file in place even if bad things happen.
14783 */
14784 OsPath GetTempCacheFilePath();
14785
14786 /**
14787 * Replays the commands.txt file in the given subdirectory visually.
14788 */
14789 bool StartVisualReplay(const OsPath& directory);
14790
14791 /**
14792 * Reads the replay Cache file and parses it into a jsObject
14793 *
14794 * @param scriptInterface - the ScriptInterface in which to create the return data.
14795 * @param cachedReplaysObject - the cached replays.
14796 * @return true on succes
14797 */
14798 bool ReadCacheFile(const ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject);
14799
14800 /**
14801 * Stores the replay list in the replay cache file
14802 *
14803 * @param scriptInterface - the ScriptInterface in which to create the return data.
14804 * @param replays - the replay list to store.
14805 */
14806 void StoreCacheFile(const ScriptInterface& scriptInterface, JS::HandleObject replays);
14807
14808 /**
14809 * Load the replay cache and check if there are new/deleted replays. If so, update the cache.
14810 *
14811 * @param scriptInterface - the ScriptInterface in which to create the return data.
14812 * @param compareFiles - compare the directory name and the FileSize of the replays and the cache.
14813 * @return cache entries
14814 */
14815 JS::HandleObject ReloadReplayCache(const ScriptInterface& scriptInterface, bool compareFiles);
14816
14817 /**
14818 * Get a list of replays to display in the GUI.
14819 *
14820 * @param scriptInterface - the ScriptInterface in which to create the return data.
14821 * @param compareFiles - reload the cache, which takes more time,
14822 * but nearly ensures, that no changed replay is missed.
14823 * @return array of objects containing replay data
14824 */
14825 JS::Value GetReplays(const ScriptInterface& scriptInterface, bool compareFiles);
14826
14827 /**
14828 * Parses a commands.txt file and extracts metadata.
14829 * Works similarly to CGame::LoadReplayData().
14830 */
14831 JS::Value LoadReplayData(const ScriptInterface& scriptInterface, const OsPath& directory);
14832
14833 /**
14834 * Permanently deletes the visual replay (including the parent directory)
14835 *
14836 * @param replayFile - path to commands.txt, whose parent directory will be deleted.
14837 * @return true if deletion was successful, false on error
14838 */
14839 bool DeleteReplay(const OsPath& replayFile);
14840
14841 /**
14842 * Returns the parsed header of the replay file (commands.txt).
14843 */
14844 JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const OsPath& directoryName);
14845
14846 /**
14847 * Returns whether or not the metadata / summary screen data has been saved properly when the game ended.
14848 */
14849 bool HasReplayMetadata(const OsPath& directoryName);
14850
14851 /**
14852 * Returns the metadata of a replay.
14853 */
14854 JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const OsPath& directoryName);
14855
14856 /**
14857 * Adds a replay to the replayCache.
14858 */
14859 void AddReplayToCache(const ScriptInterface& scriptInterface, const CStrW& directoryName);
14860 }
14861
14862 #endif
14863Index: source/soundmanager/scripting/JSInterface_Sound.cpp
14864===================================================================
14865--- source/soundmanager/scripting/JSInterface_Sound.cpp (revision 23275)
14866+++ source/soundmanager/scripting/JSInterface_Sound.cpp (working copy)
14867@@ -1,153 +1,153 @@
14868 /* Copyright (C) 2018 Wildfire Games.
14869 * This file is part of 0 A.D.
14870 *
14871 * 0 A.D. is free software: you can redistribute it and/or modify
14872 * it under the terms of the GNU General Public License as published by
14873 * the Free Software Foundation, either version 2 of the License, or
14874 * (at your option) any later version.
14875 *
14876 * 0 A.D. is distributed in the hope that it will be useful,
14877 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14878 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14879 * GNU General Public License for more details.
14880 *
14881 * You should have received a copy of the GNU General Public License
14882 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
14883 */
14884 #include "precompiled.h"
14885
14886 #include "JSInterface_Sound.h"
14887
14888 #include "lib/config2.h"
14889 #include "lib/utf8.h"
14890 #include "maths/Vector3D.h"
14891 #include "ps/Filesystem.h"
14892 #include "scriptinterface/ScriptInterface.h"
14893 #include "soundmanager/SoundManager.h"
14894
14895 #include <sstream>
14896
14897 namespace JSI_Sound
14898 {
14899 #if CONFIG2_AUDIO
14900
14901 void StartMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
14902 {
14903 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14904 sndManager->SetMusicEnabled(true);
14905 }
14906
14907 void StopMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
14908 {
14909 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14910 sndManager->SetMusicEnabled(false);
14911 }
14912
14913 void ClearPlaylist(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
14914 {
14915 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14916 sndManager->ClearPlayListItems();
14917 }
14918
14919 void AddPlaylistItem(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename)
14920 {
14921 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14922 sndManager->AddPlayListItem(VfsPath(filename));
14923 }
14924
14925 void StartPlaylist(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool looping)
14926 {
14927 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14928 sndManager->StartPlayList(looping );
14929 }
14930
14931 void PlayMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename, bool looping)
14932 {
14933 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14934 sndManager->PlayAsMusic(filename, looping);
14935 }
14936
14937 void PlayUISound(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename, bool looping)
14938 {
14939 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14940 sndManager->PlayAsUI(filename, looping);
14941 }
14942
14943 void PlayAmbientSound(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename, bool looping)
14944 {
14945 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14946 sndManager->PlayAsAmbient(filename, looping);
14947 }
14948
14949 bool MusicPlaying(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
14950 {
14951 return true;
14952 }
14953
14954 void SetMasterGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
14955 {
14956 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14957 sndManager->SetMasterGain(gain);
14958 }
14959
14960 void SetMusicGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
14961 {
14962 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14963 sndManager->SetMusicGain(gain);
14964 }
14965
14966 void SetAmbientGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
14967 {
14968 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14969 sndManager->SetAmbientGain(gain);
14970 }
14971
14972 void SetActionGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
14973 {
14974 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14975 sndManager->SetActionGain(gain);
14976 }
14977
14978 void SetUIGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
14979 {
14980 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14981 sndManager->SetUIGain(gain);
14982 }
14983
14984 #else
14985
14986 bool MusicPlaying(ScriptInterface::CxPrivate* UNUSED(pCxPrivate) ){ return false; }
14987 void PlayAmbientSound(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& UNUSED(filename), bool UNUSED(looping) ){}
14988 void PlayUISound(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& UNUSED(filename), bool UNUSED(looping) ) {}
14989 void PlayMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& UNUSED(filename), bool UNUSED(looping) ) {}
14990 void StartPlaylist(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool UNUSED(looping) ){}
14991 void AddPlaylistItem(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& UNUSED(filename) ){}
14992 void ClearPlaylist(ScriptInterface::CxPrivate* UNUSED(pCxPrivate) ){}
14993 void StopMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate) ){}
14994 void StartMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate) ){}
14995- void SetMasterGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
14996- void SetMusicGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
14997- void SetAmbientGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
14998- void SetActionGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
14999- void SetUIGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
15000+ void SetMasterGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15001+ void SetMusicGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15002+ void SetAmbientGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15003+ void SetActionGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15004+ void SetUIGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15005
15006 #endif
15007
15008 void RegisterScriptFunctions(const ScriptInterface& scriptInterface)
15009 {
15010 scriptInterface.RegisterFunction<void, &StartMusic>("StartMusic");
15011 scriptInterface.RegisterFunction<void, &StopMusic>("StopMusic");
15012 scriptInterface.RegisterFunction<void, &ClearPlaylist>("ClearPlaylist");
15013 scriptInterface.RegisterFunction<void, std::wstring, &AddPlaylistItem>("AddPlaylistItem");
15014 scriptInterface.RegisterFunction<void, bool, &StartPlaylist>("StartPlaylist");
15015 scriptInterface.RegisterFunction<void, std::wstring, bool, &PlayMusic>("PlayMusic");
15016 scriptInterface.RegisterFunction<void, std::wstring, bool, &PlayUISound>("PlayUISound");
15017 scriptInterface.RegisterFunction<void, std::wstring, bool, &PlayAmbientSound>("PlayAmbientSound");
15018 scriptInterface.RegisterFunction<bool, &MusicPlaying>("MusicPlaying");
15019 scriptInterface.RegisterFunction<void, float, &SetMasterGain>("SetMasterGain");
15020 scriptInterface.RegisterFunction<void, float, &SetMusicGain>("SetMusicGain");
15021 scriptInterface.RegisterFunction<void, float, &SetAmbientGain>("SetAmbientGain");
15022 scriptInterface.RegisterFunction<void, float, &SetActionGain>("SetActionGain");
15023 scriptInterface.RegisterFunction<void, float, &SetUIGain>("SetUIGain");
15024 }
15025 }
15026Index: source/third_party/tinygettext/src/iconv.cpp
15027===================================================================
15028--- source/third_party/tinygettext/src/iconv.cpp (revision 23275)
15029+++ source/third_party/tinygettext/src/iconv.cpp (working copy)
15030@@ -1,152 +1,152 @@
15031 // tinygettext - A gettext replacement that works directly on .po files
15032 // Copyright (c) 2009 Ingo Ruhnke <grumbel@gmail.com>
15033 //
15034 // This software is provided 'as-is', without any express or implied
15035 // warranty. In no event will the authors be held liable for any damages
15036 // arising from the use of this software.
15037 //
15038 // Permission is granted to anyone to use this software for any purpose,
15039 // including commercial applications, and to alter it and redistribute it
15040 // freely, subject to the following restrictions:
15041 //
15042 // 1. The origin of this software must not be misrepresented; you must not
15043 // claim that you wrote the original software. If you use this software
15044 // in a product, an acknowledgement in the product documentation would be
15045 // appreciated but is not required.
15046 // 2. Altered source versions must be plainly marked as such, and must not be
15047 // misrepresented as being the original software.
15048 // 3. This notice may not be removed or altered from any source distribution.
15049
15050 #include "precompiled.h"
15051
15052 #include <ctype.h>
15053 #include <assert.h>
15054 #include <sstream>
15055 #include <errno.h>
15056 #include <stdexcept>
15057 #include <string.h>
15058 #include <stdlib.h>
15059
15060 #include "tinygettext/iconv.hpp"
15061 #include "tinygettext/log_stream.hpp"
15062
15063 namespace tinygettext {
15064
15065 #ifndef tinygettext_ICONV_CONST
15066 # define tinygettext_ICONV_CONST
15067 #endif
15068
15069 IConv::IConv()
15070 : to_charset(),
15071 from_charset(),
15072 cd(0)
15073 {}
15074
15075 IConv::IConv(const std::string& from_charset_, const std::string& to_charset_)
15076 : to_charset(),
15077 from_charset(),
15078 cd(0)
15079 {
15080 set_charsets(from_charset_, to_charset_);
15081 }
15082
15083 IConv::~IConv()
15084 {
15085 if (cd)
15086 tinygettext_iconv_close(cd);
15087 }
15088
15089 void
15090 IConv::set_charsets(const std::string& from_charset_, const std::string& to_charset_)
15091 {
15092 if (cd)
15093 tinygettext_iconv_close(cd);
15094
15095 from_charset = from_charset_;
15096 to_charset = to_charset_;
15097
15098 for(std::string::iterator i = to_charset.begin(); i != to_charset.end(); ++i)
15099 *i = static_cast<char>(toupper(*i));
15100
15101 for(std::string::iterator i = from_charset.begin(); i != from_charset.end(); ++i)
15102 *i = static_cast<char>(toupper(*i));
15103
15104 if (to_charset == from_charset)
15105 {
15106 cd = 0;
15107 }
15108 else
15109 {
15110 cd = tinygettext_iconv_open(to_charset.c_str(), from_charset.c_str());
15111 if (cd == reinterpret_cast<tinygettext_iconv_t>(-1))
15112 {
15113 if(errno == EINVAL)
15114 {
15115 std::ostringstream str;
15116 str << "IConv construction failed: conversion from '" << from_charset
15117 << "' to '" << to_charset << "' not available";
15118 throw std::runtime_error(str.str());
15119 }
15120 else
15121 {
15122 std::ostringstream str;
15123 str << "IConv: construction failed: " << strerror(errno);
15124 throw std::runtime_error(str.str());
15125 }
15126 }
15127 }
15128 }
15129
15130 /// Convert a string from encoding to another.
15131 std::string
15132 IConv::convert(const std::string& text)
15133 {
15134 if (!cd)
15135 {
15136 return text;
15137 }
15138 else
15139 {
15140 size_t inbytesleft = text.size();
15141 size_t outbytesleft = 4*inbytesleft; // Worst case scenario: ASCII -> UTF-32?
15142
15143 // We try to avoid to much copying around, so we write directly into
15144 // a std::string
15145- tinygettext_ICONV_CONST char* inbuf = const_cast<char*>(&text[0]);
15146+ const char* inbuf = const_cast<char*>(&text[0]);
15147 std::string result(outbytesleft, 'X');
15148 char* outbuf = &result[0];
15149
15150 // Try to convert the text.
15151 size_t ret = tinygettext_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
15152 if (ret == static_cast<size_t>(-1))
15153 {
15154 if (errno == EILSEQ || errno == EINVAL)
15155 { // invalid multibyte sequence
15156 tinygettext_iconv(cd, NULL, NULL, NULL, NULL); // reset state
15157
15158 // FIXME: Could try to skip the invalid byte and continue
15159 log_error << "error: tinygettext:iconv: invalid multibyte sequence in: \"" << text << "\"" << std::endl;
15160 }
15161 else if (errno == E2BIG)
15162 { // output buffer to small
15163 assert(false && "tinygettext/iconv.cpp: E2BIG: This should never be reached");
15164 }
15165 else if (errno == EBADF)
15166 {
15167 assert(false && "tinygettext/iconv.cpp: EBADF: This should never be reached");
15168 }
15169 else
15170 {
15171 assert(false && "tinygettext/iconv.cpp: <unknown>: This should never be reached");
15172 }
15173 }
15174
15175 result.resize(4*text.size() - outbytesleft);
15176
15177 return result;
15178 }
15179 }
15180
15181 } // namespace tinygettext
15182
15183 /* EOF */