· 6 years ago · Dec 24, 2019, 02:15 PM
1Index: binaries/system/glooxwrapper.dll
2===================================================================
3Cannot display: file marked as a binary type.
4svn:mime-type = application/octet-stream
5Index: binaries/system/pyrogenesis.exe
6===================================================================
7Cannot display: file marked as a binary type.
8svn:mime-type = application/octet-stream
9Index: libraries/source/fcollada/src/meson.build
10===================================================================
11--- libraries/source/fcollada/src/meson.build (nonexistent)
12+++ libraries/source/fcollada/src/meson.build (working copy)
13@@ -0,0 +1,189 @@
14+cpp_args = ['-DRETAIL']
15+
16+if host_machine.system() == 'linux'
17+ cpp_args += '-DLINUX'
18+endif
19+
20+if get_option('buildtype') == 'release'
21+ cpp_args += '-DNDEBUG'
22+else
23+ cpp_args += '-D_DEBUG'
24+endif
25+
26+# FCollada is not aliasing-safe, so disallow dangerous optimisations
27+# (TODO: It'd be nice to fix FCollada, but that looks hard)
28+cpp_args += '-fno-strict-aliasing'
29+
30+sources = [
31+ 'FCollada/FCollada.cpp',
32+ 'FCollada/FColladaPlugin.cpp',
33+ 'FCollada/FCDocument/FCDAnimated.cpp',
34+ 'FCollada/FCDocument/FCDAnimationChannel.cpp',
35+ 'FCollada/FCDocument/FCDAnimationClip.cpp',
36+ 'FCollada/FCDocument/FCDAnimationClipTools.cpp',
37+ 'FCollada/FCDocument/FCDAnimation.cpp',
38+ 'FCollada/FCDocument/FCDAnimationCurve.cpp',
39+ 'FCollada/FCDocument/FCDAnimationCurveTools.cpp',
40+ 'FCollada/FCDocument/FCDAnimationKey.cpp',
41+ 'FCollada/FCDocument/FCDAnimationMultiCurve.cpp',
42+ 'FCollada/FCDocument/FCDAsset.cpp',
43+ 'FCollada/FCDocument/FCDCamera.cpp',
44+ 'FCollada/FCDocument/FCDController.cpp',
45+ 'FCollada/FCDocument/FCDControllerInstance.cpp',
46+ 'FCollada/FCDocument/FCDControllerTools.cpp',
47+ 'FCollada/FCDocument/FCDEffectCode.cpp',
48+ 'FCollada/FCDocument/FCDEffect.cpp',
49+ 'FCollada/FCDocument/FCDEffectParameter.cpp',
50+ 'FCollada/FCDocument/FCDEffectParameterFactory.cpp',
51+ 'FCollada/FCDocument/FCDEffectParameterSampler.cpp',
52+ 'FCollada/FCDocument/FCDEffectParameterSurface.cpp',
53+ 'FCollada/FCDocument/FCDEffectPass.cpp',
54+ 'FCollada/FCDocument/FCDEffectPassShader.cpp',
55+ 'FCollada/FCDocument/FCDEffectPassState.cpp',
56+ 'FCollada/FCDocument/FCDEffectProfile.cpp',
57+ 'FCollada/FCDocument/FCDEffectProfileFX.cpp',
58+ 'FCollada/FCDocument/FCDEffectStandard.cpp',
59+ 'FCollada/FCDocument/FCDEffectTechnique.cpp',
60+ 'FCollada/FCDocument/FCDEffectTools.cpp',
61+ 'FCollada/FCDocument/FCDEmitter.cpp',
62+ 'FCollada/FCDocument/FCDEmitterInstance.cpp',
63+ 'FCollada/FCDocument/FCDEmitterObject.cpp',
64+ 'FCollada/FCDocument/FCDEmitterParticle.cpp',
65+ 'FCollada/FCDocument/FCDEntity.cpp',
66+ 'FCollada/FCDocument/FCDEntityInstance.cpp',
67+ 'FCollada/FCDocument/FCDEntityReference.cpp',
68+ 'FCollada/FCDocument/FCDExternalReferenceManager.cpp',
69+ 'FCollada/FCDocument/FCDExtra.cpp',
70+ 'FCollada/FCDocument/FCDForceDeflector.cpp',
71+ 'FCollada/FCDocument/FCDForceDrag.cpp',
72+ 'FCollada/FCDocument/FCDForceField.cpp',
73+ 'FCollada/FCDocument/FCDForceGravity.cpp',
74+ 'FCollada/FCDocument/FCDForcePBomb.cpp',
75+ 'FCollada/FCDocument/FCDForceWind.cpp',
76+ 'FCollada/FCDocument/FCDGeometry.cpp',
77+ 'FCollada/FCDocument/FCDGeometryInstance.cpp',
78+ 'FCollada/FCDocument/FCDGeometryMesh.cpp',
79+ 'FCollada/FCDocument/FCDGeometryNURBSSurface.cpp',
80+ 'FCollada/FCDocument/FCDGeometryPolygons.cpp',
81+ 'FCollada/FCDocument/FCDGeometryPolygonsInput.cpp',
82+ 'FCollada/FCDocument/FCDGeometryPolygonsTools.cpp',
83+ 'FCollada/FCDocument/FCDGeometrySource.cpp',
84+ 'FCollada/FCDocument/FCDGeometrySpline.cpp',
85+ 'FCollada/FCDocument/FCDImage.cpp',
86+ 'FCollada/FCDocument/FCDLibrary.cpp',
87+ 'FCollada/FCDocument/FCDLight.cpp',
88+ 'FCollada/FCDocument/FCDLightTools.cpp',
89+ 'FCollada/FCDocument/FCDMaterial.cpp',
90+ 'FCollada/FCDocument/FCDMaterialInstance.cpp',
91+ 'FCollada/FCDocument/FCDMorphController.cpp',
92+ 'FCollada/FCDocument/FCDObject.cpp',
93+ 'FCollada/FCDocument/FCDObjectWithId.cpp',
94+ 'FCollada/FCDocument/FCDocument.cpp',
95+ 'FCollada/FCDocument/FCDocumentTools.cpp',
96+ 'FCollada/FCDocument/FCDParameterAnimatable.cpp',
97+ 'FCollada/FCDocument/FCDParticleModifier.cpp',
98+ 'FCollada/FCDocument/FCDPhysicsAnalyticalGeometry.cpp',
99+ 'FCollada/FCDocument/FCDPhysicsForceFieldInstance.cpp',
100+ 'FCollada/FCDocument/FCDPhysicsMaterial.cpp',
101+ 'FCollada/FCDocument/FCDPhysicsModel.cpp',
102+ 'FCollada/FCDocument/FCDPhysicsModelInstance.cpp',
103+ 'FCollada/FCDocument/FCDPhysicsRigidBody.cpp',
104+ 'FCollada/FCDocument/FCDPhysicsRigidBodyInstance.cpp',
105+ 'FCollada/FCDocument/FCDPhysicsRigidBodyParameters.cpp',
106+ 'FCollada/FCDocument/FCDPhysicsRigidConstraint.cpp',
107+ 'FCollada/FCDocument/FCDPhysicsRigidConstraintInstance.cpp',
108+ 'FCollada/FCDocument/FCDPhysicsScene.cpp',
109+ 'FCollada/FCDocument/FCDPhysicsShape.cpp',
110+ 'FCollada/FCDocument/FCDPlaceHolder.cpp',
111+ 'FCollada/FCDocument/FCDSceneNode.cpp',
112+ 'FCollada/FCDocument/FCDSceneNodeIterator.cpp',
113+ 'FCollada/FCDocument/FCDSceneNodeTools.cpp',
114+ 'FCollada/FCDocument/FCDSkinController.cpp',
115+ 'FCollada/FCDocument/FCDTargetedEntity.cpp',
116+ 'FCollada/FCDocument/FCDTexture.cpp',
117+ 'FCollada/FCDocument/FCDTransform.cpp',
118+ 'FCollada/FCDocument/FCDVersion.cpp',
119+ 'FCollada/FMath/FMAllocator.cpp',
120+ 'FCollada/FMath/FMAngleAxis.cpp',
121+ 'FCollada/FMath/FMColor.cpp',
122+ 'FCollada/FMath/FMInterpolation.cpp',
123+ 'FCollada/FMath/FMLookAt.cpp',
124+ 'FCollada/FMath/FMMatrix33.cpp',
125+ 'FCollada/FMath/FMMatrix44.cpp',
126+ 'FCollada/FMath/FMQuaternion.cpp',
127+ 'FCollada/FMath/FMRandom.cpp',
128+ 'FCollada/FMath/FMSkew.cpp',
129+ 'FCollada/FMath/FMVector3.cpp',
130+ 'FCollada/FMath/FMVolume.cpp',
131+ 'FCollada/FUtils/FUAssert.cpp',
132+ 'FCollada/FUtils/FUBase64.cpp',
133+ 'FCollada/FUtils/FUBoundingBox.cpp',
134+ 'FCollada/FUtils/FUBoundingSphere.cpp',
135+ 'FCollada/FUtils/FUCrc32.cpp',
136+ 'FCollada/FUtils/FUCriticalSection.cpp',
137+ 'FCollada/FUtils/FUDaeEnum.cpp',
138+ 'FCollada/FUtils/FUDateTime.cpp',
139+ 'FCollada/FUtils/FUDebug.cpp',
140+ 'FCollada/FUtils/FUError.cpp',
141+ 'FCollada/FUtils/FUErrorLog.cpp',
142+ 'FCollada/FUtils/FUFile.cpp',
143+ 'FCollada/FUtils/FUFileManager.cpp',
144+ 'FCollada/FUtils/FULogFile.cpp',
145+ 'FCollada/FUtils/FUObject.cpp',
146+ 'FCollada/FUtils/FUObjectType.cpp',
147+ 'FCollada/FUtils/FUParameter.cpp',
148+ 'FCollada/FUtils/FUParameterizable.cpp',
149+ 'FCollada/FUtils/FUPluginManager.cpp',
150+ 'FCollada/FUtils/FUSemaphore.cpp',
151+ 'FCollada/FUtils/FUStringBuilder.cpp',
152+ 'FCollada/FUtils/FUStringConversion.cpp',
153+ 'FCollada/FUtils/FUSynchronizableObject.cpp',
154+ 'FCollada/FUtils/FUThread.cpp',
155+ 'FCollada/FUtils/FUTracker.cpp',
156+ 'FCollada/FUtils/FUUniqueStringMap.cpp',
157+ 'FCollada/FUtils/FUUri.cpp',
158+ 'FCollada/FUtils/FUXmlDocument.cpp',
159+ 'FCollada/FUtils/FUXmlParser.cpp',
160+ 'FCollada/FUtils/FUXmlWriter.cpp',
161+ 'FColladaPlugins/FArchiveXML/FArchiveXML.cpp',
162+ 'FColladaPlugins/FArchiveXML/FAXAnimationExport.cpp',
163+ 'FColladaPlugins/FArchiveXML/FAXAnimationImport.cpp',
164+ 'FColladaPlugins/FArchiveXML/FAXCameraExport.cpp',
165+ 'FColladaPlugins/FArchiveXML/FAXCameraImport.cpp',
166+ 'FColladaPlugins/FArchiveXML/FAXColladaParser.cpp',
167+ 'FColladaPlugins/FArchiveXML/FAXColladaWriter.cpp',
168+ 'FColladaPlugins/FArchiveXML/FAXControllerExport.cpp',
169+ 'FColladaPlugins/FArchiveXML/FAXControllerImport.cpp',
170+ 'FColladaPlugins/FArchiveXML/FAXEmitterExport.cpp',
171+ 'FColladaPlugins/FArchiveXML/FAXEmitterImport.cpp',
172+ 'FColladaPlugins/FArchiveXML/FAXEntityExport.cpp',
173+ 'FColladaPlugins/FArchiveXML/FAXEntityImport.cpp',
174+ 'FColladaPlugins/FArchiveXML/FAXForceFieldExport.cpp',
175+ 'FColladaPlugins/FArchiveXML/FAXForceFieldImport.cpp',
176+ 'FColladaPlugins/FArchiveXML/FAXGeometryExport.cpp',
177+ 'FColladaPlugins/FArchiveXML/FAXGeometryImport.cpp',
178+ 'FColladaPlugins/FArchiveXML/FAXImportLinking.cpp',
179+ 'FColladaPlugins/FArchiveXML/FAXInstanceExport.cpp',
180+ 'FColladaPlugins/FArchiveXML/FAXInstanceImport.cpp',
181+ 'FColladaPlugins/FArchiveXML/FAXLightExport.cpp',
182+ 'FColladaPlugins/FArchiveXML/FAXLightImport.cpp',
183+ 'FColladaPlugins/FArchiveXML/FAXMaterialExport.cpp',
184+ 'FColladaPlugins/FArchiveXML/FAXMaterialImport.cpp',
185+ 'FColladaPlugins/FArchiveXML/FAXPhysicsExport.cpp',
186+ 'FColladaPlugins/FArchiveXML/FAXPhysicsImport.cpp',
187+ 'FColladaPlugins/FArchiveXML/FAXSceneExport.cpp',
188+ 'FColladaPlugins/FArchiveXML/FAXSceneImport.cpp',
189+]
190+
191+fcollada_lib = static_library(
192+ 'FCollada',
193+ sources,
194+ include_directories: 'FCollada',
195+ cpp_args: cpp_args,
196+ dependencies: [dep_dl, dep_libxml2]
197+)
198+
199+dep_fcollada = declare_dependency(
200+ include_directories: ['../include'],
201+ link_with: fcollada_lib,
202+)
203Index: libraries/source/nvtt/meson.build
204===================================================================
205--- libraries/source/nvtt/meson.build (nonexistent)
206+++ libraries/source/nvtt/meson.build (working copy)
207@@ -0,0 +1,68 @@
208+#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"
209+
210+sources = [
211+ 'src/src/nvcore/Debug.cpp',
212+ 'src/src/nvcore/Library.cpp',
213+ 'src/src/nvcore/Memory.cpp',
214+ 'src/src/nvcore/Radix.cpp',
215+ 'src/src/nvcore/StrLib.cpp',
216+ 'src/src/nvcore/TextReader.cpp',
217+ 'src/src/nvcore/TextWriter.cpp',
218+ 'src/src/nvcore/Tokenizer.cpp',
219+ 'src/src/nvimage/BlockDXT.cpp',
220+ 'src/src/nvimage/ColorBlock.cpp',
221+ 'src/src/nvimage/DirectDrawSurface.cpp',
222+ 'src/src/nvimage/Filter.cpp',
223+ 'src/src/nvimage/FloatImage.cpp',
224+ 'src/src/nvimage/HoleFilling.cpp',
225+ 'src/src/nvimage/Image.cpp',
226+ 'src/src/nvimage/ImageIO.cpp',
227+ 'src/src/nvimage/NormalMap.cpp',
228+ 'src/src/nvimage/NormalMipmap.cpp',
229+ 'src/src/nvimage/Quantize.cpp',
230+ 'src/src/nvmath/Basis.cpp',
231+ 'src/src/nvmath/Montecarlo.cpp',
232+ 'src/src/nvmath/Plane.cpp',
233+ 'src/src/nvmath/Random.cpp',
234+ 'src/src/nvmath/SphericalHarmonic.cpp',
235+ 'src/src/nvmath/Triangle.cpp',
236+ 'src/src/nvmath/TriBox.cpp',
237+ 'src/src/nvtt/CompressDXT.cpp',
238+ 'src/src/nvtt/CompressionOptions.cpp',
239+ 'src/src/nvtt/Compressor.cpp',
240+ 'src/src/nvtt/CompressRGB.cpp',
241+ 'src/src/nvtt/cuda/CudaCompressDXT.cpp',
242+ 'src/src/nvtt/cuda/CudaUtils.cpp',
243+ 'src/src/nvtt/InputOptions.cpp',
244+ 'src/src/nvtt/nvtt.cpp',
245+ 'src/src/nvtt/nvtt_wrapper.cpp',
246+ 'src/src/nvtt/OptimalCompressDXT.cpp',
247+ 'src/src/nvtt/OutputOptions.cpp',
248+ 'src/src/nvtt/QuickCompressDXT.cpp',
249+ 'src/src/nvtt/squish/colourblock.cpp',
250+ 'src/src/nvtt/squish/colourfit.cpp',
251+ 'src/src/nvtt/squish/colourset.cpp',
252+ 'src/src/nvtt/squish/maths.cpp',
253+ 'src/src/nvtt/squish/weightedclusterfit.cpp',
254+]
255+
256+config_h = configuration_data()
257+config_h.set('HAVE_UNISTD_H', 1)
258+config_h.set('HAVE_STDARG_H', 1)
259+config_h.set('HAVE_SIGNAL_H', 1)
260+config_h.set('HAVE_EXECINFO_H', 1)
261+config_h.set('HAVE_MALLOC_H', 1)
262+config_h.set('HAVE_PNG', 1)
263+config_h.set('POSH_USE_LIMITS_H', 1)
264+configure_file(output: 'nvconfig.h', configuration: config_h)
265+
266+nvtt_lib = static_library(
267+ 'nvtt',
268+ sources,
269+ include_directories: ['src/src', 'src/src/nvtt/squish'],
270+)
271+
272+dep_nvtt = declare_dependency(
273+ include_directories: ['include'],
274+ link_with: nvtt_lib,
275+)
276Index: libraries/win32/wxwidgets
277===================================================================
278--- libraries/win32/wxwidgets (revision 23275)
279+++ libraries/win32/wxwidgets (working copy)
280
281Property changes on: libraries/win32/wxwidgets
282___________________________________________________________________
283Added: svn:global-ignores
284## -0,0 +1,2 ##
285+include
286+lib
287Index: meson.build
288===================================================================
289--- meson.build (nonexistent)
290+++ meson.build (working copy)
291@@ -0,0 +1,207 @@
292+project('0ad',
293+ 'cpp',
294+ version: 'a23.1',
295+ default_options: [
296+ 'warning_level=0',
297+ ],
298+ meson_version: '>= 0.52',
299+ license: 'GPL',
300+)
301+
302+if get_option('gles')
303+ dep_gl = dependency('glesv2')
304+else
305+ dep_gl = dependency('gl')
306+endif
307+
308+dep_tinygettext = declare_dependency(
309+ include_directories: 'source/third_party/tinygettext/include',
310+)
311+
312+dep_valgrind = declare_dependency(
313+ include_directories: 'libraries/source/valgrind/include',
314+)
315+
316+dep_cxxtest = declare_dependency(
317+ include_directories: 'libraries/source/cxxtest-4.4',
318+)
319+
320+cc = meson.get_compiler('cpp')
321+
322+if host_machine.system() != 'windows'
323+ dep_dl = cc.find_library('dl', required: false)
324+ dep_boost = cc.find_library('boost_filesystem')
325+ dep_enet = dependency('libenet')
326+
327+
328+ if get_option('lobby')
329+ dep_gloox = dependency('gloox')
330+ endif
331+
332+ dep_iconv = []
333+ dep_icu = dependency('icu-i18n')
334+ dep_curl = dependency('libcurl')
335+ dep_png = dependency('libpng')
336+ dep_sodium = dependency('libsodium')
337+ dep_libxml2 = dependency('libxml-2.0')
338+
339+ if get_option('upnp')
340+ dep_miniupnpc = dependency('miniupnpc')
341+ endif
342+
343+ dep_sdl2 = dependency('SDL2')
344+
345+ if get_option('audio')
346+ dep_openal = dependency('openal')
347+ dep_vorbis = dependency('vorbisfile')
348+ endif
349+
350+ dep_zlib = dependency('zlib')
351+
352+ dep_js = declare_dependency(
353+ include_directories: 'libraries/source/spidermonkey/include-unix-release',
354+ dependencies: cc.find_library('js_static', dirs: meson.current_source_dir() / 'libraries/source/spidermonkey/mozjs-45.0.2/js/src/build-release/js/src'),
355+ )
356+ spidermonkey_include = 'libraries/source/spidermonkey/include-win32-release'
357+
358+ subdir('libraries/source/fcollada/src')
359+
360+ if get_option('nvtt')
361+ subdir('libraries/source/nvtt')
362+ endif
363+else
364+ dep_gl = declare_dependency(
365+ include_directories: 'libraries/win32/opengl/include'
366+ )
367+
368+
369+ if get_option('buildtype') == 'release'
370+ boost_dependencies = [
371+ cc.find_library('libboost_filesystem-vc140-mt-1_65_1',
372+ dirs: meson.current_source_dir() / 'libraries/win32/boost/lib'
373+ ),
374+ cc.find_library('libboost_system-vc140-mt-1_65_1',
375+ dirs: meson.current_source_dir() / 'libraries/win32/boost/lib'
376+ ),
377+ ]
378+ else
379+ boost_dependencies = [
380+ cc.find_library('libboost_filesystem-vc140-mt-gd-1_65_1',
381+ dirs: meson.current_source_dir() / 'libraries/win32/boost/lib'
382+ ),
383+ cc.find_library('libboost_system-vc140-mt-gd-1_65_1',
384+ dirs: meson.current_source_dir() / 'libraries/win32/boost/lib'
385+ ),
386+ ]
387+ endif
388+
389+ dep_boost = declare_dependency(
390+ include_directories: 'libraries/win32/boost/include',
391+ dependencies: boost_dependencies
392+ )
393+
394+ dep_enet = declare_dependency(
395+ include_directories: 'libraries/win32/enet/include',
396+ dependencies: cc.find_library('enet', dirs: meson.current_source_dir() / 'libraries/win32/enet/lib'),
397+ )
398+
399+ if get_option('lobby')
400+ dep_gloox = declare_dependency(
401+ include_directories: 'libraries/win32/gloox/include',
402+ dependencies: cc.find_library('gloox-1.0', dirs: meson.current_source_dir() / 'libraries/win32/gloox/lib'),
403+ )
404+ endif
405+
406+ dep_iconv = declare_dependency(
407+ include_directories: 'libraries/win32/iconv/include',
408+ dependencies: cc.find_library('libiconv', dirs: meson.current_source_dir() / 'libraries/win32/iconv/lib'),
409+ )
410+
411+ dep_icu = declare_dependency(
412+ include_directories: 'libraries/win32/icu/include',
413+ dependencies: [
414+ cc.find_library('icudt', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
415+ cc.find_library('icuin', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
416+ cc.find_library('icuio', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
417+ cc.find_library('icule', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
418+ cc.find_library('iculx', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
419+ cc.find_library('icutu', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
420+ cc.find_library('icuuc', dirs: meson.current_source_dir() / 'libraries/win32/icu/lib'),
421+ ],
422+ )
423+
424+ dep_curl = declare_dependency(
425+ include_directories: 'libraries/win32/libcurl/include',
426+ dependencies: cc.find_library('libcurl', dirs: meson.current_source_dir() / 'libraries/win32/libcurl/lib'),
427+ )
428+
429+ dep_png = declare_dependency(
430+ include_directories: 'libraries/win32/libpng/include',
431+ dependencies: cc.find_library('libpng16', dirs: meson.current_source_dir() / 'libraries/win32/libpng/lib'),
432+ )
433+
434+ dep_sodium = declare_dependency(
435+ include_directories: 'libraries/win32/libsodium/include',
436+ dependencies: cc.find_library('libsodium', dirs: meson.current_source_dir() / 'libraries/win32/libsodium/lib'),
437+ )
438+
439+ dep_libxml2 = declare_dependency(
440+ include_directories: 'libraries/win32/libxml2/include',
441+ dependencies: cc.find_library('libxml2', dirs: meson.current_source_dir() / 'libraries/win32/libxml2/lib'),
442+ )
443+
444+ if get_option('upnp')
445+ dep_miniupnpc = declare_dependency(
446+ include_directories: 'libraries/win32/miniupnpc/include',
447+ dependencies: cc.find_library('miniupnpc', dirs: meson.current_source_dir() / 'libraries/win32/miniupnpc/lib'),
448+ )
449+ endif
450+
451+ dep_sdl2 = declare_dependency(
452+ include_directories: 'libraries/win32/sdl2/include/SDL',
453+ dependencies: [
454+ cc.find_library('SDL2', dirs: meson.current_source_dir() / 'libraries/win32/sdl2/lib'),
455+ cc.find_library('SDL2main', dirs: meson.current_source_dir() / 'libraries/win32/sdl2/lib'),
456+ ]
457+ )
458+
459+ if get_option('audio')
460+ dep_openal = declare_dependency(
461+ include_directories: 'libraries/win32/openal/include',
462+ dependencies: cc.find_library('OpenAL32', dirs: meson.current_source_dir() / 'libraries/win32/openal/lib'),
463+ )
464+
465+ dep_vorbis = declare_dependency(
466+ include_directories: 'libraries/win32/vorbis/include',
467+ dependencies: [
468+ cc.find_library('libogg', dirs: meson.current_source_dir() / 'libraries/win32/vorbis/lib'),
469+ cc.find_library('libvorbis', dirs: meson.current_source_dir() / 'libraries/win32/vorbis/lib'),
470+ cc.find_library('libvorbisfile', dirs: meson.current_source_dir() / 'libraries/win32/vorbis/lib'),
471+ ]
472+ )
473+ endif
474+
475+ dep_zlib = declare_dependency(
476+ include_directories: 'libraries/win32/zlib/include',
477+ dependencies: cc.find_library('zlib1', dirs: meson.current_source_dir() / 'libraries/win32/zlib/lib/'),
478+ )
479+
480+ dep_fcollada = declare_dependency(
481+ include_directories: 'libraries/source/fcollada/include',
482+ dependencies: cc.find_library('FCollada', dirs: meson.current_source_dir() / 'libraries/source/fcollada/lib'),
483+ )
484+
485+ dep_js = declare_dependency(
486+ include_directories: 'libraries/source/spidermonkey/include-win32-release',
487+ dependencies: cc.find_library('mozjs45-ps-release-vc140', dirs: meson.current_source_dir() / 'libraries/source/spidermonkey/lib'),
488+ )
489+
490+ if get_option('nvtt')
491+ dep_nvtt = declare_dependency(
492+ include_directories: 'libraries/source/nvtt/include',
493+ dependencies: cc.find_library('nvtt', dirs: meson.current_source_dir() / 'libraries/source/nvtt/lib')
494+ )
495+ endif
496+ endif
497+
498+subdir('source')
499Index: meson_options.txt
500===================================================================
501--- meson_options.txt (nonexistent)
502+++ meson_options.txt (working copy)
503@@ -0,0 +1,48 @@
504+option(
505+ 'gles',
506+ type: 'boolean',
507+ value: false,
508+ description: 'Build with OpenGL ES support instead of OpenGL'
509+)
510+
511+option(
512+ 'audio',
513+ type: 'boolean',
514+ value: true,
515+ description: 'Build with audio support'
516+)
517+
518+option(
519+ 'lobby',
520+ type: 'boolean',
521+ value: true,
522+ description: 'Build with XMPP support'
523+)
524+
525+option(
526+ 'upnp',
527+ type: 'boolean',
528+ value: true,
529+ description: 'Build with UPnP support'
530+)
531+
532+option(
533+ 'nvtt',
534+ type: 'boolean',
535+ value: true,
536+ description: 'Build with NVTT support'
537+)
538+
539+option(
540+ 'atlas',
541+ type: 'boolean',
542+ value: false,
543+ description: 'Build Atlas the map editor'
544+)
545+
546+option(
547+ 'collada',
548+ type: 'boolean',
549+ value: true,
550+ description: 'Build the collada wrapper'
551+)
552Index: source/gui/ObjectTypes/CInput.cpp
553===================================================================
554--- source/gui/ObjectTypes/CInput.cpp (revision 23275)
555+++ source/gui/ObjectTypes/CInput.cpp (working copy)
556@@ -1,2051 +1,2052 @@
557 /* Copyright (C) 2019 Wildfire Games.
558 * This file is part of 0 A.D.
559 *
560 * 0 A.D. is free software: you can redistribute it and/or modify
561 * it under the terms of the GNU General Public License as published by
562 * the Free Software Foundation, either version 2 of the License, or
563 * (at your option) any later version.
564 *
565 * 0 A.D. is distributed in the hope that it will be useful,
566 * but WITHOUT ANY WARRANTY; without even the implied warranty of
567 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
568 * GNU General Public License for more details.
569 *
570 * You should have received a copy of the GNU General Public License
571 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
572 */
573
574 #include "precompiled.h"
575
576 #include "CInput.h"
577
578 #include "graphics/FontMetrics.h"
579 #include "graphics/ShaderManager.h"
580 #include "graphics/TextRenderer.h"
581 #include "gui/CGUI.h"
582 #include "gui/CGUIScrollBarVertical.h"
583-#include "lib/sysdep/clipboard.h"
584 #include "lib/timer.h"
585 #include "lib/utf8.h"
586 #include "ps/ConfigDB.h"
587 #include "ps/GameSetup/Config.h"
588 #include "ps/Globals.h"
589 #include "ps/Hotkey.h"
590 #include "renderer/Renderer.h"
591
592 #include <sstream>
593
594 extern int g_yres;
595
596 CInput::CInput(CGUI& pGUI)
597 :
598 IGUIObject(pGUI),
599 IGUIScrollBarOwner(*static_cast<IGUIObject*>(this)),
600 m_iBufferPos(-1),
601 m_iBufferPos_Tail(-1),
602 m_SelectingText(),
603 m_HorizontalScroll(),
604 m_PrevTime(),
605 m_CursorVisState(true),
606 m_CursorBlinkRate(0.5),
607 m_ComposingText(),
608 m_iComposedLength(),
609 m_iComposedPos(),
610 m_iInsertPos(),
611 m_BufferPosition(),
612 m_BufferZone(),
613 m_Caption(),
614 m_CellID(),
615 m_Font(),
616 m_MaskChar(),
617 m_Mask(),
618 m_MaxLength(),
619 m_MultiLine(),
620 m_Readonly(),
621 m_ScrollBar(),
622 m_ScrollBarStyle(),
623 m_Sprite(),
624 m_SpriteSelectArea(),
625 m_TextColor(),
626 m_TextColorSelected()
627 {
628 RegisterSetting("buffer_position", m_BufferPosition);
629 RegisterSetting("buffer_zone", m_BufferZone);
630 RegisterSetting("caption", m_Caption);
631 RegisterSetting("cell_id", m_CellID);
632 RegisterSetting("font", m_Font);
633 RegisterSetting("mask_char", m_MaskChar);
634 RegisterSetting("mask", m_Mask);
635 RegisterSetting("max_length", m_MaxLength);
636 RegisterSetting("multiline", m_MultiLine);
637 RegisterSetting("readonly", m_Readonly);
638 RegisterSetting("scrollbar", m_ScrollBar);
639 RegisterSetting("scrollbar_style", m_ScrollBarStyle);
640 RegisterSetting("sprite", m_Sprite);
641 RegisterSetting("sprite_selectarea", m_SpriteSelectArea);
642 RegisterSetting("textcolor", m_TextColor);
643 RegisterSetting("textcolor_selected", m_TextColorSelected);
644
645 CFG_GET_VAL("gui.cursorblinkrate", m_CursorBlinkRate);
646
647 CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI);
648 bar->SetRightAligned(true);
649 AddScrollBar(bar);
650 }
651
652 CInput::~CInput()
653 {
654 }
655
656 void CInput::UpdateBufferPositionSetting()
657 {
658 SetSetting<i32>("buffer_position", m_iBufferPos, false);
659 }
660
661 void CInput::ClearComposedText()
662 {
663 m_Caption.erase(m_iInsertPos, m_iComposedLength);
664 m_iBufferPos = m_iInsertPos;
665 UpdateBufferPositionSetting();
666 m_iComposedLength = 0;
667 m_iComposedPos = 0;
668 }
669
670 InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
671 {
672 ENSURE(m_iBufferPos != -1);
673
674 switch (ev->ev.type)
675 {
676 case SDL_HOTKEYDOWN:
677 {
678 if (m_ComposingText)
679 return IN_HANDLED;
680
681 return ManuallyHandleHotkeyEvent(ev);
682 }
683 // SDL2 has a new method of text input that better supports Unicode and CJK
684 // see https://wiki.libsdl.org/Tutorials/TextInput
685 case SDL_TEXTINPUT:
686 {
687 if (m_Readonly)
688 return IN_PASS;
689
690 // Text has been committed, either single key presses or through an IME
691 std::wstring text = wstring_from_utf8(ev->ev.text.text);
692
693 m_WantedX = 0.0f;
694
695 if (SelectingText())
696 DeleteCurSelection();
697
698 if (m_ComposingText)
699 {
700 ClearComposedText();
701 m_ComposingText = false;
702 }
703
704 if (m_iBufferPos == static_cast<int>(m_Caption.length()))
705 m_Caption.append(text);
706 else
707 m_Caption.insert(m_iBufferPos, text);
708
709 UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
710
711 m_iBufferPos += text.length();
712 UpdateBufferPositionSetting();
713 m_iBufferPos_Tail = -1;
714
715 UpdateAutoScroll();
716 SendEvent(GUIM_TEXTEDIT, "textedit");
717
718 return IN_HANDLED;
719 }
720 case SDL_TEXTEDITING:
721 {
722 if (m_Readonly)
723 return IN_PASS;
724
725 // Text is being composed with an IME
726 // TODO: indicate this by e.g. underlining the uncommitted text
727 const char* rawText = ev->ev.edit.text;
728 int rawLength = strlen(rawText);
729 std::wstring wtext = wstring_from_utf8(rawText);
730
731 debug_printf("SDL_TEXTEDITING: text=%s, start=%d, length=%d\n", rawText, ev->ev.edit.start, ev->ev.edit.length);
732 m_WantedX = 0.0f;
733
734 if (SelectingText())
735 DeleteCurSelection();
736
737 // Remember cursor position when text composition begins
738 if (!m_ComposingText)
739 m_iInsertPos = m_iBufferPos;
740 else
741 {
742 // Composed text is replaced each time
743 ClearComposedText();
744 }
745
746 m_ComposingText = ev->ev.edit.start != 0 || rawLength != 0;
747 if (m_ComposingText)
748 {
749 m_Caption.insert(m_iInsertPos, wtext);
750
751 // The text buffer is limited to SDL_TEXTEDITINGEVENT_TEXT_SIZE bytes, yet start
752 // increases without limit, so don't let it advance beyond the composed text length
753 m_iComposedLength = wtext.length();
754 m_iComposedPos = ev->ev.edit.start < m_iComposedLength ? ev->ev.edit.start : m_iComposedLength;
755 m_iBufferPos = m_iInsertPos + m_iComposedPos;
756
757 // TODO: composed text selection - what does ev.edit.length do?
758 m_iBufferPos_Tail = -1;
759 }
760
761 UpdateBufferPositionSetting();
762 UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
763
764 UpdateAutoScroll();
765 SendEvent(GUIM_TEXTEDIT, "textedit");
766
767 return IN_HANDLED;
768 }
769 case SDL_KEYDOWN:
770 {
771 if (m_ComposingText)
772 return IN_HANDLED;
773
774 // Since the GUI framework doesn't handle to set settings
775 // in Unicode (CStrW), we'll simply retrieve the actual
776 // pointer and edit that.
777 SDL_Keycode keyCode = ev->ev.key.keysym.sym;
778
779 ManuallyImmutableHandleKeyDownEvent(keyCode);
780 ManuallyMutableHandleKeyDownEvent(keyCode);
781
782 UpdateBufferPositionSetting();
783 return IN_HANDLED;
784 }
785 default:
786 {
787 return IN_PASS;
788 }
789 }
790 }
791
792 void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode)
793 {
794 if (m_Readonly)
795 return;
796
797 wchar_t cooked = 0;
798
799 switch (keyCode)
800 {
801 case SDLK_TAB:
802 {
803 SendEvent(GUIM_TAB, "tab");
804 // Don't send a textedit event, because it should only
805 // be sent if the GUI control changes the text
806 break;
807 }
808 case SDLK_BACKSPACE:
809 {
810 m_WantedX = 0.0f;
811
812 if (SelectingText())
813 DeleteCurSelection();
814 else
815 {
816 m_iBufferPos_Tail = -1;
817
818 if (m_Caption.empty() || m_iBufferPos == 0)
819 break;
820
821 if (m_iBufferPos == static_cast<int>(m_Caption.length()))
822 m_Caption = m_Caption.Left(static_cast<long>(m_Caption.length()) - 1);
823 else
824 m_Caption =
825 m_Caption.Left(m_iBufferPos - 1) +
826 m_Caption.Right(static_cast<long>(m_Caption.length()) - m_iBufferPos);
827
828 --m_iBufferPos;
829
830 UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos);
831 }
832
833 UpdateAutoScroll();
834 SendEvent(GUIM_TEXTEDIT, "textedit");
835 break;
836 }
837 case SDLK_DELETE:
838 {
839 m_WantedX = 0.0f;
840
841 if (SelectingText())
842 DeleteCurSelection();
843 else
844 {
845 if (m_Caption.empty() || m_iBufferPos == static_cast<int>(m_Caption.length()))
846 break;
847
848 m_Caption =
849 m_Caption.Left(m_iBufferPos) +
850 m_Caption.Right(static_cast<long>(m_Caption.length()) - (m_iBufferPos + 1));
851
852 UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos);
853 }
854
855 UpdateAutoScroll();
856 SendEvent(GUIM_TEXTEDIT, "textedit");
857 break;
858 }
859 case SDLK_KP_ENTER:
860 case SDLK_RETURN:
861 {
862 // 'Return' should do a Press event for single liners (e.g. submitting forms)
863 // otherwise a '\n' character will be added.
864 if (!m_MultiLine)
865 {
866 SendEvent(GUIM_PRESSED, "press");
867 break;
868 }
869
870 cooked = '\n'; // Change to '\n' and do default:
871 FALLTHROUGH;
872 }
873 default: // Insert a character
874 {
875 // In SDL2, we no longer get Unicode wchars via SDL_Keysym
876 // we use text input events instead and they provide UTF-8 chars
877 if (cooked == 0)
878 return;
879
880 // check max length
881 if (m_MaxLength != 0 && static_cast<int>(m_Caption.length()) >= m_MaxLength)
882 break;
883
884 m_WantedX = 0.0f;
885
886 if (SelectingText())
887 DeleteCurSelection();
888 m_iBufferPos_Tail = -1;
889
890 if (m_iBufferPos == static_cast<int>(m_Caption.length()))
891 m_Caption += cooked;
892 else
893 m_Caption =
894 m_Caption.Left(m_iBufferPos) + cooked +
895 m_Caption.Right(static_cast<long>(m_Caption.length()) - m_iBufferPos);
896
897 UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos + 1);
898
899 ++m_iBufferPos;
900
901 UpdateAutoScroll();
902 SendEvent(GUIM_TEXTEDIT, "textedit");
903 break;
904 }
905 }
906 }
907
908 void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode)
909 {
910 bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT];
911
912 switch (keyCode)
913 {
914 case SDLK_HOME:
915 {
916 // If there's not a selection, we should create one now
917 if (!shiftKeyPressed)
918 {
919 // Make sure a selection isn't created.
920 m_iBufferPos_Tail = -1;
921 }
922 else if (!SelectingText())
923 {
924 // Place tail at the current point:
925 m_iBufferPos_Tail = m_iBufferPos;
926 }
927
928 m_iBufferPos = 0;
929 m_WantedX = 0.0f;
930
931 UpdateAutoScroll();
932 break;
933 }
934 case SDLK_END:
935 {
936 // If there's not a selection, we should create one now
937 if (!shiftKeyPressed)
938 {
939 // Make sure a selection isn't created.
940 m_iBufferPos_Tail = -1;
941 }
942 else if (!SelectingText())
943 {
944 // Place tail at the current point:
945 m_iBufferPos_Tail = m_iBufferPos;
946 }
947
948 m_iBufferPos = static_cast<long>(m_Caption.length());
949 m_WantedX = 0.0f;
950
951 UpdateAutoScroll();
952 break;
953 }
954 /**
955 * Conventions for Left/Right when text is selected:
956 *
957 * References:
958 *
959 * Visual Studio
960 * Visual Studio has the 'newer' approach, used by newer versions of
961 * things, and in newer applications. A left press will always place
962 * the pointer on the left edge of the selection, and then of course
963 * remove the selection. Right will do the exact same thing.
964 * If you have the pointer on the right edge and press right, it will
965 * in other words just remove the selection.
966 *
967 * Windows (eg. Notepad)
968 * A left press always takes the pointer a step to the left and
969 * removes the selection as if it were never there in the first place.
970 * Right of course does the same thing but to the right.
971 *
972 * I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN
973 * Messenger.
974 */
975 case SDLK_LEFT:
976 {
977 m_WantedX = 0.f;
978
979 if (shiftKeyPressed || !SelectingText())
980 {
981 if (!shiftKeyPressed)
982 m_iBufferPos_Tail = -1;
983 else if (!SelectingText())
984 m_iBufferPos_Tail = m_iBufferPos;
985
986 if (m_iBufferPos > 0)
987 --m_iBufferPos;
988 }
989 else
990 {
991 if (m_iBufferPos_Tail < m_iBufferPos)
992 m_iBufferPos = m_iBufferPos_Tail;
993
994 m_iBufferPos_Tail = -1;
995 }
996
997 UpdateAutoScroll();
998 break;
999 }
1000 case SDLK_RIGHT:
1001 {
1002 m_WantedX = 0.0f;
1003
1004 if (shiftKeyPressed || !SelectingText())
1005 {
1006 if (!shiftKeyPressed)
1007 m_iBufferPos_Tail = -1;
1008 else if (!SelectingText())
1009 m_iBufferPos_Tail = m_iBufferPos;
1010
1011 if (m_iBufferPos < static_cast<int>(m_Caption.length()))
1012 ++m_iBufferPos;
1013 }
1014 else
1015 {
1016 if (m_iBufferPos_Tail > m_iBufferPos)
1017 m_iBufferPos = m_iBufferPos_Tail;
1018
1019 m_iBufferPos_Tail = -1;
1020 }
1021
1022 UpdateAutoScroll();
1023 break;
1024 }
1025 /**
1026 * Conventions for Up/Down when text is selected:
1027 *
1028 * References:
1029 *
1030 * Visual Studio
1031 * Visual Studio has a very strange approach, down takes you below the
1032 * selection to the next row, and up to the one prior to the whole
1033 * selection. The weird part is that it is always aligned as the
1034 * 'pointer'. I decided this is to much work for something that is
1035 * a bit arbitrary
1036 *
1037 * Windows (eg. Notepad)
1038 * Just like with left/right, the selection is destroyed and it moves
1039 * just as if there never were a selection.
1040 *
1041 * I chose the Notepad convention even though I use the VS convention with
1042 * left/right.
1043 */
1044 case SDLK_UP:
1045 {
1046 if (!shiftKeyPressed)
1047 m_iBufferPos_Tail = -1;
1048 else if (!SelectingText())
1049 m_iBufferPos_Tail = m_iBufferPos;
1050
1051 std::list<SRow>::iterator current = m_CharacterPositions.begin();
1052 while (current != m_CharacterPositions.end())
1053 {
1054 if (m_iBufferPos >= current->m_ListStart &&
1055 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
1056 break;
1057
1058 ++current;
1059 }
1060
1061 float pos_x;
1062 if (m_iBufferPos - current->m_ListStart == 0)
1063 pos_x = 0.f;
1064 else
1065 pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1];
1066
1067 if (m_WantedX > pos_x)
1068 pos_x = m_WantedX;
1069
1070 // Now change row:
1071 if (current != m_CharacterPositions.begin())
1072 {
1073 --current;
1074
1075 // Find X-position:
1076 m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
1077 }
1078 // else we can't move up
1079
1080 UpdateAutoScroll();
1081 break;
1082 }
1083 case SDLK_DOWN:
1084 {
1085 if (!shiftKeyPressed)
1086 m_iBufferPos_Tail = -1;
1087 else if (!SelectingText())
1088 m_iBufferPos_Tail = m_iBufferPos;
1089
1090 std::list<SRow>::iterator current = m_CharacterPositions.begin();
1091 while (current != m_CharacterPositions.end())
1092 {
1093 if (m_iBufferPos >= current->m_ListStart &&
1094 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
1095 break;
1096
1097 ++current;
1098 }
1099
1100 float pos_x;
1101
1102 if (m_iBufferPos - current->m_ListStart == 0)
1103 pos_x = 0.f;
1104 else
1105 pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1];
1106
1107 if (m_WantedX > pos_x)
1108 pos_x = m_WantedX;
1109
1110 // Now change row:
1111 // Add first, so we can check if it's .end()
1112 ++current;
1113 if (current != m_CharacterPositions.end())
1114 {
1115 // Find X-position:
1116 m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
1117 }
1118 // else we can't move up
1119
1120 UpdateAutoScroll();
1121 break;
1122 }
1123 case SDLK_PAGEUP:
1124 {
1125 GetScrollBar(0).ScrollMinusPlenty();
1126 UpdateAutoScroll();
1127 break;
1128 }
1129 case SDLK_PAGEDOWN:
1130 {
1131 GetScrollBar(0).ScrollPlusPlenty();
1132 UpdateAutoScroll();
1133 break;
1134 }
1135 default:
1136 {
1137 break;
1138 }
1139 }
1140 }
1141
1142 InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
1143 {
1144 bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT];
1145
1146 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
1147
1148 if (hotkey == "paste")
1149 {
1150 if (m_Readonly)
1151 return IN_PASS;
1152
1153 m_WantedX = 0.0f;
1154
1155- wchar_t* text = sys_clipboard_get();
1156+ char* text = SDL_GetClipboardText();
1157 if (text)
1158 {
1159+ std::wstring wstring = wstring_from_utf8(text);
1160+ const wchar_t* wtext = wstring.c_str();
1161 if (SelectingText())
1162 DeleteCurSelection();
1163
1164 if (m_iBufferPos == static_cast<int>(m_Caption.length()))
1165- m_Caption += text;
1166+ m_Caption += wtext;
1167 else
1168 m_Caption =
1169- m_Caption.Left(m_iBufferPos) + text +
1170+ m_Caption.Left(m_iBufferPos) + wtext +
1171 m_Caption.Right(static_cast<long>(m_Caption.length()) - m_iBufferPos);
1172
1173 UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
1174
1175- m_iBufferPos += (int)wcslen(text);
1176+ m_iBufferPos += static_cast<int>(wcslen(wtext));
1177 UpdateAutoScroll();
1178 UpdateBufferPositionSetting();
1179
1180- sys_clipboard_free(text);
1181+ SDL_free(text);
1182
1183 SendEvent(GUIM_TEXTEDIT, "textedit");
1184 }
1185
1186 return IN_HANDLED;
1187 }
1188 else if (hotkey == "copy" || hotkey == "cut")
1189 {
1190 if (m_Readonly && hotkey == "cut")
1191 return IN_PASS;
1192
1193 m_WantedX = 0.0f;
1194
1195 if (SelectingText())
1196 {
1197 int virtualFrom;
1198 int virtualTo;
1199
1200 if (m_iBufferPos_Tail >= m_iBufferPos)
1201 {
1202 virtualFrom = m_iBufferPos;
1203 virtualTo = m_iBufferPos_Tail;
1204 }
1205 else
1206 {
1207 virtualFrom = m_iBufferPos_Tail;
1208 virtualTo = m_iBufferPos;
1209 }
1210
1211 CStrW text = m_Caption.Left(virtualTo).Right(virtualTo - virtualFrom);
1212
1213- sys_clipboard_set(&text[0]);
1214+ SDL_SetClipboardText(text.ToUTF8().c_str());
1215
1216 if (hotkey == "cut")
1217 {
1218 DeleteCurSelection();
1219 UpdateAutoScroll();
1220 SendEvent(GUIM_TEXTEDIT, "textedit");
1221 }
1222 }
1223
1224 return IN_HANDLED;
1225 }
1226 else if (hotkey == "text.delete.left")
1227 {
1228 if (m_Readonly)
1229 return IN_PASS;
1230
1231 m_WantedX = 0.0f;
1232
1233 if (SelectingText())
1234 DeleteCurSelection();
1235
1236 if (!m_Caption.empty() && m_iBufferPos != 0)
1237 {
1238 m_iBufferPos_Tail = m_iBufferPos;
1239 CStrW searchString = m_Caption.Left(m_iBufferPos);
1240
1241 // If we are starting in whitespace, adjust position until we get a non whitespace
1242 while (m_iBufferPos > 0)
1243 {
1244 if (!iswspace(searchString[m_iBufferPos - 1]))
1245 break;
1246
1247 m_iBufferPos--;
1248 }
1249
1250 // If we end up on a punctuation char we just delete it (treat punct like a word)
1251 if (iswpunct(searchString[m_iBufferPos - 1]))
1252 m_iBufferPos--;
1253 else
1254 {
1255 // Now we are on a non white space character, adjust position to char after next whitespace char is found
1256 while (m_iBufferPos > 0)
1257 {
1258 if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1]))
1259 break;
1260
1261 m_iBufferPos--;
1262 }
1263 }
1264
1265 UpdateBufferPositionSetting();
1266 DeleteCurSelection();
1267 SendEvent(GUIM_TEXTEDIT, "textedit");
1268 }
1269 UpdateAutoScroll();
1270 return IN_HANDLED;
1271 }
1272 else if (hotkey == "text.delete.right")
1273 {
1274 if (m_Readonly)
1275 return IN_PASS;
1276
1277 m_WantedX = 0.0f;
1278
1279 if (SelectingText())
1280 DeleteCurSelection();
1281
1282 if (!m_Caption.empty() && m_iBufferPos < static_cast<int>(m_Caption.length()))
1283 {
1284 // Delete the word to the right of the cursor
1285 m_iBufferPos_Tail = m_iBufferPos;
1286
1287 // Delete chars to the right unit we hit whitespace
1288 while (++m_iBufferPos < static_cast<int>(m_Caption.length()))
1289 {
1290 if (iswspace(m_Caption[m_iBufferPos]) || iswpunct(m_Caption[m_iBufferPos]))
1291 break;
1292 }
1293
1294 // Eliminate any whitespace behind the word we just deleted
1295 while (m_iBufferPos < static_cast<int>(m_Caption.length()))
1296 {
1297 if (!iswspace(m_Caption[m_iBufferPos]))
1298 break;
1299
1300 ++m_iBufferPos;
1301 }
1302 UpdateBufferPositionSetting();
1303 DeleteCurSelection();
1304 }
1305 UpdateAutoScroll();
1306 SendEvent(GUIM_TEXTEDIT, "textedit");
1307 return IN_HANDLED;
1308 }
1309 else if (hotkey == "text.move.left")
1310 {
1311 m_WantedX = 0.0f;
1312
1313 if (shiftKeyPressed || !SelectingText())
1314 {
1315 if (!shiftKeyPressed)
1316 m_iBufferPos_Tail = -1;
1317 else if (!SelectingText())
1318 m_iBufferPos_Tail = m_iBufferPos;
1319
1320 if (!m_Caption.empty() && m_iBufferPos != 0)
1321 {
1322 CStrW searchString = m_Caption.Left(m_iBufferPos);
1323
1324 // If we are starting in whitespace, adjust position until we get a non whitespace
1325 while (m_iBufferPos > 0)
1326 {
1327 if (!iswspace(searchString[m_iBufferPos - 1]))
1328 break;
1329
1330 m_iBufferPos--;
1331 }
1332
1333 // If we end up on a puctuation char we just select it (treat punct like a word)
1334 if (iswpunct(searchString[m_iBufferPos - 1]))
1335 m_iBufferPos--;
1336 else
1337 {
1338 // Now we are on a non white space character, adjust position to char after next whitespace char is found
1339 while (m_iBufferPos > 0)
1340 {
1341 if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1]))
1342 break;
1343
1344 m_iBufferPos--;
1345 }
1346 }
1347 }
1348 }
1349 else
1350 {
1351 if (m_iBufferPos_Tail < m_iBufferPos)
1352 m_iBufferPos = m_iBufferPos_Tail;
1353
1354 m_iBufferPos_Tail = -1;
1355 }
1356
1357 UpdateBufferPositionSetting();
1358 UpdateAutoScroll();
1359
1360 return IN_HANDLED;
1361 }
1362 else if (hotkey == "text.move.right")
1363 {
1364 m_WantedX = 0.0f;
1365
1366 if (shiftKeyPressed || !SelectingText())
1367 {
1368 if (!shiftKeyPressed)
1369 m_iBufferPos_Tail = -1;
1370 else if (!SelectingText())
1371 m_iBufferPos_Tail = m_iBufferPos;
1372
1373 if (!m_Caption.empty() && m_iBufferPos < static_cast<int>(m_Caption.length()))
1374 {
1375 // Select chars to the right until we hit whitespace
1376 while (++m_iBufferPos < static_cast<int>(m_Caption.length()))
1377 {
1378 if (iswspace(m_Caption[m_iBufferPos]) || iswpunct(m_Caption[m_iBufferPos]))
1379 break;
1380 }
1381
1382 // Also select any whitespace following the word we just selected
1383 while (m_iBufferPos < static_cast<int>(m_Caption.length()))
1384 {
1385 if (!iswspace(m_Caption[m_iBufferPos]))
1386 break;
1387
1388 ++m_iBufferPos;
1389 }
1390 }
1391 }
1392 else
1393 {
1394 if (m_iBufferPos_Tail > m_iBufferPos)
1395 m_iBufferPos = m_iBufferPos_Tail;
1396
1397 m_iBufferPos_Tail = -1;
1398 }
1399
1400 UpdateBufferPositionSetting();
1401 UpdateAutoScroll();
1402
1403 return IN_HANDLED;
1404 }
1405
1406 return IN_PASS;
1407 }
1408
1409 void CInput::ResetStates()
1410 {
1411 IGUIObject::ResetStates();
1412 IGUIScrollBarOwner::ResetStates();
1413 }
1414
1415 void CInput::HandleMessage(SGUIMessage& Message)
1416 {
1417 IGUIObject::HandleMessage(Message);
1418 IGUIScrollBarOwner::HandleMessage(Message);
1419
1420 switch (Message.type)
1421 {
1422 case GUIM_SETTINGS_UPDATED:
1423 {
1424 // Update scroll-bar
1425 // TODO Gee: (2004-09-01) Is this really updated each time it should?
1426 if (m_ScrollBar &&
1427 (Message.value == "size" ||
1428 Message.value == "z" ||
1429 Message.value == "absolute"))
1430 {
1431 GetScrollBar(0).SetX(m_CachedActualSize.right);
1432 GetScrollBar(0).SetY(m_CachedActualSize.top);
1433 GetScrollBar(0).SetZ(GetBufferedZ());
1434 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
1435 }
1436
1437 // Update scrollbar
1438 if (Message.value == "scrollbar_style")
1439 GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
1440
1441 if (Message.value == "buffer_position")
1442 {
1443 m_iBufferPos = m_BufferPosition;
1444 m_iBufferPos_Tail = -1; // position change resets selection
1445 }
1446
1447 if (Message.value == "size" ||
1448 Message.value == "z" ||
1449 Message.value == "font" ||
1450 Message.value == "absolute" ||
1451 Message.value == "caption" ||
1452 Message.value == "scrollbar" ||
1453 Message.value == "scrollbar_style")
1454 {
1455 UpdateText();
1456 }
1457
1458 if (Message.value == "multiline")
1459 {
1460 if (!m_MultiLine)
1461 GetScrollBar(0).SetLength(0.f);
1462 else
1463 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
1464
1465 UpdateText();
1466 }
1467
1468 UpdateAutoScroll();
1469
1470 break;
1471 }
1472 case GUIM_MOUSE_PRESS_LEFT:
1473 {
1474 // Check if we're selecting the scrollbar
1475 if (m_ScrollBar &&
1476 m_MultiLine &&
1477 GetScrollBar(0).GetStyle())
1478 {
1479 if (m_pGUI.GetMousePos().x > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width)
1480 break;
1481 }
1482
1483 if (m_ComposingText)
1484 break;
1485
1486 // Okay, this section is about pressing the mouse and
1487 // choosing where the point should be placed. For
1488 // instance, if we press between a and b, the point
1489 // should of course be placed accordingly. Other
1490 // special cases are handled like the input box norms.
1491 if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT])
1492 m_iBufferPos = GetMouseHoveringTextPosition();
1493 else
1494 m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
1495
1496 m_SelectingText = true;
1497
1498 UpdateAutoScroll();
1499
1500 // If we immediately release the button it will just be seen as a click
1501 // for the user though.
1502 break;
1503 }
1504 case GUIM_MOUSE_DBLCLICK_LEFT:
1505 {
1506 if (m_ComposingText)
1507 break;
1508
1509 if (m_Caption.empty())
1510 break;
1511
1512 m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
1513
1514 if (m_iBufferPos >= (int)m_Caption.length())
1515 m_iBufferPos = m_iBufferPos_Tail = m_Caption.length() - 1;
1516
1517 // See if we are clicking over whitespace
1518 if (iswspace(m_Caption[m_iBufferPos]))
1519 {
1520 // see if we are in a section of whitespace greater than one character
1521 if ((m_iBufferPos + 1 < (int) m_Caption.length() && iswspace(m_Caption[m_iBufferPos + 1])) ||
1522 (m_iBufferPos - 1 > 0 && iswspace(m_Caption[m_iBufferPos - 1])))
1523 {
1524 //
1525 // We are clicking in an area with more than one whitespace character
1526 // so we select both the word to the left and then the word to the right
1527 //
1528 // [1] First the left
1529 // skip the whitespace
1530 while (m_iBufferPos > 0)
1531 {
1532 if (!iswspace(m_Caption[m_iBufferPos - 1]))
1533 break;
1534
1535 m_iBufferPos--;
1536 }
1537 // now go until we hit white space or punctuation
1538 while (m_iBufferPos > 0)
1539 {
1540 if (iswspace(m_Caption[m_iBufferPos - 1]))
1541 break;
1542
1543 m_iBufferPos--;
1544
1545 if (iswpunct(m_Caption[m_iBufferPos]))
1546 break;
1547 }
1548
1549 // [2] Then the right
1550 // go right until we are not in whitespace
1551 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1552 {
1553 if (!iswspace(m_Caption[m_iBufferPos_Tail]))
1554 break;
1555 }
1556
1557 if (m_iBufferPos_Tail == static_cast<int>(m_Caption.length()))
1558 break;
1559
1560 // now go to the right until we hit whitespace or punctuation
1561 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1562 {
1563 if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail]))
1564 break;
1565 }
1566 }
1567 else
1568 {
1569 // single whitespace so select word to the right
1570 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1571 {
1572 if (!iswspace(m_Caption[m_iBufferPos_Tail]))
1573 break;
1574 }
1575
1576 if (m_iBufferPos_Tail == static_cast<int>(m_Caption.length()))
1577 break;
1578
1579 // Don't include the leading whitespace
1580 m_iBufferPos = m_iBufferPos_Tail;
1581
1582 // now go to the right until we hit whitespace or punctuation
1583 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1584 {
1585 if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail]))
1586 break;
1587 }
1588 }
1589 }
1590 else
1591 {
1592 // clicked on non-whitespace so select current word
1593 // go until we hit white space or punctuation
1594 while (m_iBufferPos > 0)
1595 {
1596 if (iswspace(m_Caption[m_iBufferPos - 1]))
1597 break;
1598
1599 m_iBufferPos--;
1600
1601 if (iswpunct(m_Caption[m_iBufferPos]))
1602 break;
1603 }
1604 // go to the right until we hit whitespace or punctuation
1605 while (++m_iBufferPos_Tail < static_cast<int>(m_Caption.length()))
1606 if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail]))
1607 break;
1608 }
1609 UpdateAutoScroll();
1610 break;
1611 }
1612 case GUIM_MOUSE_RELEASE_LEFT:
1613 {
1614 if (m_SelectingText)
1615 m_SelectingText = false;
1616 break;
1617 }
1618 case GUIM_MOUSE_MOTION:
1619 {
1620 // If we just pressed down and started to move before releasing
1621 // this is one way of selecting larger portions of text.
1622 if (m_SelectingText)
1623 {
1624 // Actually, first we need to re-check that the mouse button is
1625 // really pressed (it can be released while outside the control.
1626 if (!g_mouse_buttons[SDL_BUTTON_LEFT])
1627 m_SelectingText = false;
1628 else
1629 m_iBufferPos = GetMouseHoveringTextPosition();
1630 UpdateAutoScroll();
1631 }
1632 break;
1633 }
1634 case GUIM_LOAD:
1635 {
1636 GetScrollBar(0).SetX(m_CachedActualSize.right);
1637 GetScrollBar(0).SetY(m_CachedActualSize.top);
1638 GetScrollBar(0).SetZ(GetBufferedZ());
1639 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
1640 GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
1641
1642 UpdateText();
1643 UpdateAutoScroll();
1644
1645 break;
1646 }
1647 case GUIM_GOT_FOCUS:
1648 {
1649 m_iBufferPos = 0;
1650 m_PrevTime = 0.0;
1651 m_CursorVisState = false;
1652
1653 // Tell the IME where to draw the candidate list
1654 SDL_Rect rect;
1655 rect.h = m_CachedActualSize.GetSize().cy;
1656 rect.w = m_CachedActualSize.GetSize().cx;
1657 rect.x = m_CachedActualSize.TopLeft().x;
1658 rect.y = m_CachedActualSize.TopLeft().y;
1659 SDL_SetTextInputRect(&rect);
1660 SDL_StartTextInput();
1661 break;
1662 }
1663 case GUIM_LOST_FOCUS:
1664 {
1665 if (m_ComposingText)
1666 {
1667 // Simulate a final text editing event to clear the composition
1668 SDL_Event_ evt;
1669 evt.ev.type = SDL_TEXTEDITING;
1670 evt.ev.edit.length = 0;
1671 evt.ev.edit.start = 0;
1672 evt.ev.edit.text[0] = 0;
1673 ManuallyHandleEvent(&evt);
1674 }
1675 SDL_StopTextInput();
1676
1677 m_iBufferPos = -1;
1678 m_iBufferPos_Tail = -1;
1679 break;
1680 }
1681 default:
1682 {
1683 break;
1684 }
1685 }
1686 UpdateBufferPositionSetting();
1687 }
1688
1689 void CInput::UpdateCachedSize()
1690 {
1691 // If an ancestor's size changed, this will let us intercept the change and
1692 // update our scrollbar positions
1693
1694 IGUIObject::UpdateCachedSize();
1695
1696 if (m_ScrollBar)
1697 {
1698 GetScrollBar(0).SetX(m_CachedActualSize.right);
1699 GetScrollBar(0).SetY(m_CachedActualSize.top);
1700 GetScrollBar(0).SetZ(GetBufferedZ());
1701 GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
1702 }
1703 }
1704
1705 void CInput::Draw()
1706 {
1707 float bz = GetBufferedZ();
1708
1709 if (m_CursorBlinkRate > 0.0)
1710 {
1711 // check if the cursor visibility state needs to be changed
1712 double currTime = timer_Time();
1713 if (currTime - m_PrevTime >= m_CursorBlinkRate)
1714 {
1715 m_CursorVisState = !m_CursorVisState;
1716 m_PrevTime = currTime;
1717 }
1718 }
1719 else
1720 // should always be visible
1721 m_CursorVisState = true;
1722
1723 // First call draw on ScrollBarOwner
1724 if (m_ScrollBar && m_MultiLine)
1725 IGUIScrollBarOwner::Draw();
1726
1727 CStrIntern font_name(m_Font.ToUTF8());
1728
1729 wchar_t mask_char = L'*';
1730 if (m_Mask && m_MaskChar.length() > 0)
1731 mask_char = m_MaskChar[0];
1732
1733 m_pGUI.DrawSprite(m_Sprite, m_CellID, bz, m_CachedActualSize);
1734
1735 float scroll = 0.f;
1736 if (m_ScrollBar && m_MultiLine)
1737 scroll = GetScrollBar(0).GetPos();
1738
1739 CFontMetrics font(font_name);
1740
1741 // We'll have to setup clipping manually, since we're doing the rendering manually.
1742 CRect cliparea(m_CachedActualSize);
1743
1744 // First we'll figure out the clipping area, which is the cached actual size
1745 // substracted by an optional scrollbar
1746 if (m_ScrollBar)
1747 {
1748 scroll = GetScrollBar(0).GetPos();
1749
1750 // substract scrollbar from cliparea
1751 if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
1752 cliparea.right <= GetScrollBar(0).GetOuterRect().right)
1753 cliparea.right = GetScrollBar(0).GetOuterRect().left;
1754
1755 if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
1756 cliparea.left < GetScrollBar(0).GetOuterRect().right)
1757 cliparea.left = GetScrollBar(0).GetOuterRect().right;
1758 }
1759
1760 if (cliparea != CRect())
1761 {
1762 glEnable(GL_SCISSOR_TEST);
1763 glScissor(
1764 cliparea.left * g_GuiScale,
1765 g_yres - cliparea.bottom * g_GuiScale,
1766 cliparea.GetWidth() * g_GuiScale,
1767 cliparea.GetHeight() * g_GuiScale);
1768 }
1769
1770 // These are useful later.
1771 int VirtualFrom, VirtualTo;
1772
1773 if (m_iBufferPos_Tail >= m_iBufferPos)
1774 {
1775 VirtualFrom = m_iBufferPos;
1776 VirtualTo = m_iBufferPos_Tail;
1777 }
1778 else
1779 {
1780 VirtualFrom = m_iBufferPos_Tail;
1781 VirtualTo = m_iBufferPos;
1782 }
1783
1784 // Get the height of this font.
1785 float h = (float)font.GetHeight();
1786 float ls = (float)font.GetLineSpacing();
1787
1788 CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
1789
1790 CTextRenderer textRenderer(tech->GetShader());
1791 textRenderer.Font(font_name);
1792
1793 // Set the Z to somewhat more, so we can draw a selected area between the
1794 // the control and the text.
1795 textRenderer.Translate(
1796 (float)(int)(m_CachedActualSize.left) + m_BufferZone,
1797 (float)(int)(m_CachedActualSize.top+h) + m_BufferZone,
1798 bz+0.1f);
1799
1800 // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE
1801 // (sort of like a | which is aligned to the left of most characters)
1802
1803 float buffered_y = -scroll + m_BufferZone;
1804
1805 // When selecting larger areas, we need to draw a rectangle box
1806 // around it, and this is to keep track of where the box
1807 // started, because we need to follow the iteration until we
1808 // reach the end, before we can actually draw it.
1809 bool drawing_box = false;
1810 float box_x = 0.f;
1811
1812 float x_pointer = 0.f;
1813
1814 // If we have a selecting box (i.e. when you have selected letters, not just when
1815 // the pointer is between two letters) we need to process all letters once
1816 // before we do it the second time and render all the text. We can't do it
1817 // in the same loop because text will have been drawn, so it will disappear when
1818 // drawn behind the text that has already been drawn. Confusing, well it's necessary
1819 // (I think).
1820
1821 if (SelectingText())
1822 {
1823 // Now m_iBufferPos_Tail can be of both sides of m_iBufferPos,
1824 // just like you can select from right to left, as you can
1825 // left to right. Is there a difference? Yes, the pointer
1826 // be placed accordingly, so that if you select shift and
1827 // expand this selection, it will expand on appropriate side.
1828 // Anyway, since the drawing procedure needs "To" to be
1829 // greater than from, we need virtual values that might switch
1830 // place.
1831
1832 int VirtualFrom, VirtualTo;
1833
1834 if (m_iBufferPos_Tail >= m_iBufferPos)
1835 {
1836 VirtualFrom = m_iBufferPos;
1837 VirtualTo = m_iBufferPos_Tail;
1838 }
1839 else
1840 {
1841 VirtualFrom = m_iBufferPos_Tail;
1842 VirtualTo = m_iBufferPos;
1843 }
1844
1845
1846 bool done = false;
1847 for (std::list<SRow>::const_iterator it = m_CharacterPositions.begin();
1848 it != m_CharacterPositions.end();
1849 ++it, buffered_y += ls, x_pointer = 0.f)
1850 {
1851 if (m_MultiLine && buffered_y > m_CachedActualSize.GetHeight())
1852 break;
1853
1854 // We might as well use 'i' here to iterate, because we need it
1855 // (often compared against ints, so don't make it size_t)
1856 for (int i = 0; i < (int)it->m_ListOfX.size()+2; ++i)
1857 {
1858 if (it->m_ListStart + i == VirtualFrom)
1859 {
1860 // we won't actually draw it now, because we don't
1861 // know the width of each glyph to that position.
1862 // we need to go along with the iteration, and
1863 // make a mark where the box started:
1864 drawing_box = true; // will turn false when finally rendered.
1865
1866 // Get current x position
1867 box_x = x_pointer;
1868 }
1869
1870 const bool at_end = (i == (int)it->m_ListOfX.size()+1);
1871
1872 if (drawing_box && (it->m_ListStart + i == VirtualTo || at_end))
1873 {
1874 // Depending on if it's just a row change, or if it's
1875 // the end of the select box, do slightly different things.
1876 if (at_end)
1877 {
1878 if (it->m_ListStart + i != VirtualFrom)
1879 // and actually add a white space! yes, this is done in any common input
1880 x_pointer += font.GetCharacterWidth(L' ');
1881 }
1882 else
1883 {
1884 drawing_box = false;
1885 done = true;
1886 }
1887
1888 CRect rect;
1889 // Set 'rect' depending on if it's a multiline control, or a one-line control
1890 if (m_MultiLine)
1891 {
1892 rect = CRect(
1893 m_CachedActualSize.left + box_x + m_BufferZone,
1894 m_CachedActualSize.top + buffered_y + (h - ls) / 2,
1895 m_CachedActualSize.left + x_pointer + m_BufferZone,
1896 m_CachedActualSize.top + buffered_y + (h + ls) / 2);
1897
1898 if (rect.bottom < m_CachedActualSize.top)
1899 continue;
1900
1901 if (rect.top < m_CachedActualSize.top)
1902 rect.top = m_CachedActualSize.top;
1903
1904 if (rect.bottom > m_CachedActualSize.bottom)
1905 rect.bottom = m_CachedActualSize.bottom;
1906 }
1907 else // if one-line
1908 {
1909 rect = CRect(
1910 m_CachedActualSize.left + box_x + m_BufferZone - m_HorizontalScroll,
1911 m_CachedActualSize.top + buffered_y + (h - ls) / 2,
1912 m_CachedActualSize.left + x_pointer + m_BufferZone - m_HorizontalScroll,
1913 m_CachedActualSize.top + buffered_y + (h + ls) / 2);
1914
1915 if (rect.left < m_CachedActualSize.left)
1916 rect.left = m_CachedActualSize.left;
1917
1918 if (rect.right > m_CachedActualSize.right)
1919 rect.right = m_CachedActualSize.right;
1920 }
1921
1922 m_pGUI.DrawSprite(m_SpriteSelectArea, m_CellID, bz + 0.05f, rect);
1923 }
1924
1925 if (i < (int)it->m_ListOfX.size())
1926 {
1927 if (!m_Mask)
1928 x_pointer += font.GetCharacterWidth(m_Caption[it->m_ListStart + i]);
1929 else
1930 x_pointer += font.GetCharacterWidth(mask_char);
1931 }
1932 }
1933
1934 if (done)
1935 break;
1936
1937 // If we're about to draw a box, and all of a sudden changes
1938 // line, we need to draw that line's box, and then reset
1939 // the box drawing to the beginning of the new line.
1940 if (drawing_box)
1941 box_x = 0.f;
1942 }
1943 }
1944
1945 // Reset some from previous run
1946 buffered_y = -scroll;
1947
1948 // Setup initial color (then it might change and change back, when drawing selected area)
1949 textRenderer.Color(m_TextColor);
1950
1951 tech->BeginPass();
1952
1953 bool using_selected_color = false;
1954
1955 for (std::list<SRow>::const_iterator it = m_CharacterPositions.begin();
1956 it != m_CharacterPositions.end();
1957 ++it, buffered_y += ls)
1958 {
1959 if (buffered_y + m_BufferZone >= -ls || !m_MultiLine)
1960 {
1961 if (m_MultiLine && buffered_y + m_BufferZone > m_CachedActualSize.GetHeight())
1962 break;
1963
1964 CMatrix3D savedTransform = textRenderer.GetTransform();
1965
1966 // Text must always be drawn in integer values. So we have to convert scroll
1967 if (m_MultiLine)
1968 textRenderer.Translate(0.f, -(float)(int)scroll, 0.f);
1969 else
1970 textRenderer.Translate(-(float)(int)m_HorizontalScroll, 0.f, 0.f);
1971
1972 // We might as well use 'i' here, because we need it
1973 // (often compared against ints, so don't make it size_t)
1974 for (int i = 0; i < (int)it->m_ListOfX.size()+1; ++i)
1975 {
1976 if (!m_MultiLine && i < (int)it->m_ListOfX.size())
1977 {
1978 if (it->m_ListOfX[i] - m_HorizontalScroll < -m_BufferZone)
1979 {
1980 // We still need to translate the OpenGL matrix
1981 if (i == 0)
1982 textRenderer.Translate(it->m_ListOfX[i], 0.f, 0.f);
1983 else
1984 textRenderer.Translate(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f);
1985
1986 continue;
1987 }
1988 }
1989
1990 // End of selected area, change back color
1991 if (SelectingText() && it->m_ListStart + i == VirtualTo)
1992 {
1993 using_selected_color = false;
1994 textRenderer.Color(m_TextColor);
1995 }
1996
1997 // selecting only one, then we need only to draw a cursor.
1998 if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos && m_CursorVisState)
1999 textRenderer.Put(0.0f, 0.0f, L"_");
2000
2001 // Drawing selected area
2002 if (SelectingText() &&
2003 it->m_ListStart + i >= VirtualFrom &&
2004 it->m_ListStart + i < VirtualTo &&
2005 !using_selected_color)
2006 {
2007 using_selected_color = true;
2008 textRenderer.Color(m_TextColorSelected);
2009 }
2010
2011 if (i != (int)it->m_ListOfX.size())
2012 {
2013 if (!m_Mask)
2014 textRenderer.PrintfAdvance(L"%lc", m_Caption[it->m_ListStart + i]);
2015 else
2016 textRenderer.PrintfAdvance(L"%lc", mask_char);
2017 }
2018
2019 // check it's now outside a one-liner, then we'll break
2020 if (!m_MultiLine && i < (int)it->m_ListOfX.size() &&
2021 it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth() - m_BufferZone)
2022 break;
2023 }
2024
2025 if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos)
2026 {
2027 textRenderer.Color(m_TextColor);
2028 if (m_CursorVisState)
2029 textRenderer.PutAdvance(L"_");
2030
2031 if (using_selected_color)
2032 textRenderer.Color(m_TextColorSelected);
2033 }
2034
2035 textRenderer.SetTransform(savedTransform);
2036 }
2037
2038 textRenderer.Translate(0.f, ls, 0.f);
2039 }
2040
2041 textRenderer.Render();
2042
2043 if (cliparea != CRect())
2044 glDisable(GL_SCISSOR_TEST);
2045
2046 tech->EndPass();
2047 }
2048
2049 void CInput::UpdateText(int from, int to_before, int to_after)
2050 {
2051 CStrIntern font_name(m_Font.ToUTF8());
2052
2053 wchar_t mask_char = L'*';
2054 if (m_Mask && m_MaskChar.length() > 0)
2055 mask_char = m_MaskChar[0];
2056
2057 // Ensure positions are valid after caption changes
2058 m_iBufferPos = std::min(m_iBufferPos, static_cast<int>(m_Caption.size()));
2059 m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, static_cast<int>(m_Caption.size()));
2060 UpdateBufferPositionSetting();
2061
2062 if (font_name.empty())
2063 {
2064 // Destroy everything stored, there's no font, so there can be no data.
2065 m_CharacterPositions.clear();
2066 return;
2067 }
2068
2069 SRow row;
2070 row.m_ListStart = 0;
2071
2072 int to = 0; // make sure it's initialized
2073
2074 if (to_before == -1)
2075 to = static_cast<int>(m_Caption.length());
2076
2077 CFontMetrics font(font_name);
2078
2079 std::list<SRow>::iterator current_line;
2080
2081 // Used to ... TODO
2082 int check_point_row_start = -1;
2083 int check_point_row_end = -1;
2084
2085 // Reset
2086 if (from == 0 && to_before == -1)
2087 {
2088 m_CharacterPositions.clear();
2089 current_line = m_CharacterPositions.begin();
2090 }
2091 else
2092 {
2093 ENSURE(to_before != -1);
2094
2095 std::list<SRow>::iterator destroy_row_from;
2096 std::list<SRow>::iterator destroy_row_to;
2097 // Used to check if the above has been set to anything,
2098 // previously a comparison like:
2099 // destroy_row_from == std::list<SRow>::iterator()
2100 // ... was used, but it didn't work with GCC.
2101 bool destroy_row_from_used = false;
2102 bool destroy_row_to_used = false;
2103
2104 // Iterate, and remove everything between 'from' and 'to_before'
2105 // actually remove the entire lines they are on, it'll all have
2106 // to be redone. And when going along, we'll delete a row at a time
2107 // when continuing to see how much more after 'to' we need to remake.
2108 int i = 0;
2109 for (std::list<SRow>::iterator it = m_CharacterPositions.begin();
2110 it != m_CharacterPositions.end();
2111 ++it, ++i)
2112 {
2113 if (!destroy_row_from_used && it->m_ListStart > from)
2114 {
2115 // Destroy the previous line, and all to 'to_before'
2116 destroy_row_from = it;
2117 --destroy_row_from;
2118
2119 destroy_row_from_used = true;
2120
2121 // For the rare case that we might remove characters to a word
2122 // so that it suddenly fits on the previous row,
2123 // we need to by standards re-do the whole previous line too
2124 // (if one exists)
2125 if (destroy_row_from != m_CharacterPositions.begin())
2126 --destroy_row_from;
2127 }
2128
2129 if (!destroy_row_to_used && it->m_ListStart > to_before)
2130 {
2131 destroy_row_to = it;
2132 destroy_row_to_used = true;
2133
2134 // If it isn't the last row, we'll add another row to delete,
2135 // just so we can see if the last restorted line is
2136 // identical to what it was before. If it isn't, then we'll
2137 // have to continue.
2138 // 'check_point_row_start' is where we store how the that
2139 // line looked.
2140 if (destroy_row_to != m_CharacterPositions.end())
2141 {
2142 check_point_row_start = destroy_row_to->m_ListStart;
2143 check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
2144 if (destroy_row_to->m_ListOfX.empty())
2145 ++check_point_row_end;
2146 }
2147
2148 ++destroy_row_to;
2149 break;
2150 }
2151 }
2152
2153 if (!destroy_row_from_used)
2154 {
2155 destroy_row_from = m_CharacterPositions.end();
2156 --destroy_row_from;
2157
2158 // As usual, let's destroy another row back
2159 if (destroy_row_from != m_CharacterPositions.begin())
2160 --destroy_row_from;
2161
2162 current_line = destroy_row_from;
2163 }
2164
2165 if (!destroy_row_to_used)
2166 {
2167 destroy_row_to = m_CharacterPositions.end();
2168 check_point_row_start = -1;
2169 }
2170
2171 // set 'from' to the row we'll destroy from
2172 // and 'to' to the row we'll destroy to
2173 from = destroy_row_from->m_ListStart;
2174
2175 if (destroy_row_to != m_CharacterPositions.end())
2176 to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to.
2177 else
2178 to = static_cast<int>(m_Caption.length());
2179
2180
2181 // Setup the first row
2182 row.m_ListStart = destroy_row_from->m_ListStart;
2183
2184 std::list<SRow>::iterator temp_it = destroy_row_to;
2185 --temp_it;
2186
2187 current_line = m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
2188
2189 // If there has been a change in number of characters
2190 // we need to change all m_ListStart that comes after
2191 // the interval we just destroyed. We'll change all
2192 // values with the delta change of the string length.
2193 int delta = to_after - to_before;
2194 if (delta != 0)
2195 {
2196 for (std::list<SRow>::iterator it = current_line;
2197 it != m_CharacterPositions.end();
2198 ++it)
2199 it->m_ListStart += delta;
2200
2201 // Update our check point too!
2202 check_point_row_start += delta;
2203 check_point_row_end += delta;
2204
2205 if (to != static_cast<int>(m_Caption.length()))
2206 to += delta;
2207 }
2208 }
2209
2210 int last_word_started = from;
2211 float x_pos = 0.f;
2212
2213 //if (to_before != -1)
2214 // return;
2215
2216 for (int i = from; i < to; ++i)
2217 {
2218 if (m_Caption[i] == L'\n' && m_MultiLine)
2219 {
2220 if (i == to-1 && to != static_cast<int>(m_Caption.length()))
2221 break; // it will be added outside
2222
2223 current_line = m_CharacterPositions.insert(current_line, row);
2224 ++current_line;
2225
2226 // Setup the next row:
2227 row.m_ListOfX.clear();
2228 row.m_ListStart = i+1;
2229 x_pos = 0.f;
2230 }
2231 else
2232 {
2233 if (m_Caption[i] == L' '/* || TODO Gee (2004-10-13): the '-' disappears, fix.
2234 m_Caption[i] == L'-'*/)
2235 last_word_started = i+1;
2236
2237 if (!m_Mask)
2238 x_pos += font.GetCharacterWidth(m_Caption[i]);
2239 else
2240 x_pos += font.GetCharacterWidth(mask_char);
2241
2242 if (x_pos >= GetTextAreaWidth() && m_MultiLine)
2243 {
2244 // The following decides whether it will word-wrap a word,
2245 // or if it's only one word on the line, where it has to
2246 // break the word apart.
2247 if (last_word_started == row.m_ListStart)
2248 {
2249 last_word_started = i;
2250 row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started));
2251 //row.m_ListOfX.push_back(x_pos);
2252 //continue;
2253 }
2254 else
2255 {
2256 // regular word-wrap
2257 row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1));
2258 }
2259
2260 // Now, create a new line:
2261 // notice: when we enter a newline, you can stand with the cursor
2262 // both before and after that character, being on different
2263 // rows. With automatic word-wrapping, that is not possible. Which
2264 // is intuitively correct.
2265
2266 current_line = m_CharacterPositions.insert(current_line, row);
2267 ++current_line;
2268
2269 // Setup the next row:
2270 row.m_ListOfX.clear();
2271 row.m_ListStart = last_word_started;
2272
2273 i = last_word_started-1;
2274
2275 x_pos = 0.f;
2276 }
2277 else
2278 // Get width of this character:
2279 row.m_ListOfX.push_back(x_pos);
2280 }
2281
2282 // Check if it's the last iteration, and we're not revising the whole string
2283 // because in that case, more word-wrapping might be needed.
2284 // also check if the current line isn't the end
2285 if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end())
2286 {
2287 // check all rows and see if any existing
2288 if (row.m_ListStart != check_point_row_start)
2289 {
2290 std::list<SRow>::iterator destroy_row_from;
2291 std::list<SRow>::iterator destroy_row_to;
2292 // Are used to check if the above has been set to anything,
2293 // previously a comparison like:
2294 // destroy_row_from == std::list<SRow>::iterator()
2295 // was used, but it didn't work with GCC.
2296 bool destroy_row_from_used = false;
2297 bool destroy_row_to_used = false;
2298
2299 // Iterate, and remove everything between 'from' and 'to_before'
2300 // actually remove the entire lines they are on, it'll all have
2301 // to be redone. And when going along, we'll delete a row at a time
2302 // when continuing to see how much more after 'to' we need to remake.
2303 int i = 0;
2304 for (std::list<SRow>::iterator it = m_CharacterPositions.begin();
2305 it != m_CharacterPositions.end();
2306 ++it, ++i)
2307 {
2308 if (!destroy_row_from_used && it->m_ListStart > check_point_row_start)
2309 {
2310 // Destroy the previous line, and all to 'to_before'
2311 //if (i >= 2)
2312 // destroy_row_from = it-2;
2313 //else
2314 // destroy_row_from = it-1;
2315 destroy_row_from = it;
2316 destroy_row_from_used = true;
2317 //--destroy_row_from;
2318 }
2319
2320 if (!destroy_row_to_used && it->m_ListStart > check_point_row_end)
2321 {
2322 destroy_row_to = it;
2323 destroy_row_to_used = true;
2324
2325 // If it isn't the last row, we'll add another row to delete,
2326 // just so we can see if the last restorted line is
2327 // identical to what it was before. If it isn't, then we'll
2328 // have to continue.
2329 // 'check_point_row_start' is where we store how the that
2330 // line looked.
2331 if (destroy_row_to != m_CharacterPositions.end())
2332 {
2333 check_point_row_start = destroy_row_to->m_ListStart;
2334 check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
2335 if (destroy_row_to->m_ListOfX.empty())
2336 ++check_point_row_end;
2337 }
2338 else
2339 check_point_row_start = check_point_row_end = -1;
2340
2341 ++destroy_row_to;
2342 break;
2343 }
2344 }
2345
2346 if (!destroy_row_from_used)
2347 {
2348 destroy_row_from = m_CharacterPositions.end();
2349 --destroy_row_from;
2350
2351 current_line = destroy_row_from;
2352 }
2353
2354 if (!destroy_row_to_used)
2355 {
2356 destroy_row_to = m_CharacterPositions.end();
2357 check_point_row_start = check_point_row_end = -1;
2358 }
2359
2360 if (destroy_row_to != m_CharacterPositions.end())
2361 to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to.
2362 else
2363 to = static_cast<int>(m_Caption.length());
2364
2365
2366 // Set current line, new rows will be added before current_line, so
2367 // we'll choose the destroy_row_to, because it won't be deleted
2368 // in the coming erase.
2369 current_line = destroy_row_to;
2370
2371 m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
2372 }
2373 // else, the for loop will end naturally.
2374 }
2375 }
2376 // This is kind of special, when we renew a some lines, then the last
2377 // one will sometimes end with a space (' '), that really should
2378 // be omitted when word-wrapping. So we'll check if the last row
2379 // we'll add has got the same value as the next row.
2380 if (current_line != m_CharacterPositions.end())
2381 {
2382 if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart)
2383 row.m_ListOfX.resize(row.m_ListOfX.size()-1);
2384 }
2385
2386 // add the final row (even if empty)
2387 m_CharacterPositions.insert(current_line, row);
2388
2389 if (m_ScrollBar)
2390 {
2391 GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + m_BufferZone * 2.f);
2392 GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight());
2393 }
2394 }
2395
2396 int CInput::GetMouseHoveringTextPosition() const
2397 {
2398 if (m_CharacterPositions.empty())
2399 return 0;
2400
2401 // Return position
2402 int retPosition;
2403
2404 std::list<SRow>::const_iterator current = m_CharacterPositions.begin();
2405
2406 CPos mouse = m_pGUI.GetMousePos();
2407
2408 if (m_MultiLine)
2409 {
2410 float scroll = 0.f;
2411 if (m_ScrollBar)
2412 scroll = GetScrollBarPos(0);
2413
2414 // Now get the height of the font.
2415 // TODO: Get the real font
2416 CFontMetrics font(CStrIntern(m_Font.ToUTF8()));
2417 float spacing = (float)font.GetLineSpacing();
2418
2419 // Change mouse position relative to text.
2420 mouse -= m_CachedActualSize.TopLeft();
2421 mouse.x -= m_BufferZone;
2422 mouse.y += scroll - m_BufferZone;
2423
2424 int row = (int)((mouse.y) / spacing);
2425
2426 if (row < 0)
2427 row = 0;
2428
2429 if (row > (int)m_CharacterPositions.size()-1)
2430 row = (int)m_CharacterPositions.size()-1;
2431
2432 // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to
2433 // be able to get the specific element here. This is hopefully a temporary hack.
2434
2435 for (int i = 0; i < row; ++i)
2436 ++current;
2437 }
2438 else
2439 {
2440 // current is already set to begin,
2441 // but we'll change the mouse.x to fit our horizontal scrolling
2442 mouse -= m_CachedActualSize.TopLeft();
2443 mouse.x -= m_BufferZone - m_HorizontalScroll;
2444 // mouse.y is moot
2445 }
2446
2447 retPosition = current->m_ListStart;
2448
2449 // Okay, now loop through the glyphs to find the appropriate X position
2450 float dummy;
2451 retPosition += GetXTextPosition(current, mouse.x, dummy);
2452
2453 return retPosition;
2454 }
2455
2456 // Does not process horizontal scrolling, 'x' must be modified before inputted.
2457 int CInput::GetXTextPosition(const std::list<SRow>::const_iterator& current, const float& x, float& wanted) const
2458 {
2459 int ret = 0;
2460 float previous = 0.f;
2461 int i = 0;
2462
2463 for (std::vector<float>::const_iterator it = current->m_ListOfX.begin();
2464 it != current->m_ListOfX.end();
2465 ++it, ++i)
2466 {
2467 if (*it >= x)
2468 {
2469 if (x - previous >= *it - x)
2470 ret += i+1;
2471 else
2472 ret += i;
2473
2474 break;
2475 }
2476 previous = *it;
2477 }
2478 // If a position wasn't found, we will assume the last
2479 // character of that line.
2480 if (i == (int)current->m_ListOfX.size())
2481 {
2482 ret += i;
2483 wanted = x;
2484 }
2485 else
2486 wanted = 0.f;
2487
2488 return ret;
2489 }
2490
2491 void CInput::DeleteCurSelection()
2492 {
2493 int virtualFrom;
2494 int virtualTo;
2495
2496 if (m_iBufferPos_Tail >= m_iBufferPos)
2497 {
2498 virtualFrom = m_iBufferPos;
2499 virtualTo = m_iBufferPos_Tail;
2500 }
2501 else
2502 {
2503 virtualFrom = m_iBufferPos_Tail;
2504 virtualTo = m_iBufferPos;
2505 }
2506
2507 m_Caption =
2508 m_Caption.Left(virtualFrom) +
2509 m_Caption.Right(static_cast<long>(m_Caption.length()) - virtualTo);
2510
2511 UpdateText(virtualFrom, virtualTo, virtualFrom);
2512
2513 // Remove selection
2514 m_iBufferPos_Tail = -1;
2515 m_iBufferPos = virtualFrom;
2516 UpdateBufferPositionSetting();
2517 }
2518
2519 bool CInput::SelectingText() const
2520 {
2521 return m_iBufferPos_Tail != -1 &&
2522 m_iBufferPos_Tail != m_iBufferPos;
2523 }
2524
2525 float CInput::GetTextAreaWidth()
2526 {
2527 if (m_ScrollBar && GetScrollBar(0).GetStyle())
2528 return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f - GetScrollBar(0).GetStyle()->m_Width;
2529
2530 return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f;
2531 }
2532
2533 void CInput::UpdateAutoScroll()
2534 {
2535 // Autoscrolling up and down
2536 if (m_MultiLine)
2537 {
2538 if (!m_ScrollBar)
2539 return;
2540
2541 const float scroll = GetScrollBar(0).GetPos();
2542
2543 // Now get the height of the font.
2544 // TODO: Get the real font
2545 CFontMetrics font(CStrIntern(m_Font.ToUTF8()));
2546 float spacing = (float)font.GetLineSpacing();
2547 //float height = font.GetHeight();
2548
2549 // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to
2550 // be able to get the specific element here. This is hopefully a temporary hack.
2551
2552 std::list<SRow>::iterator current = m_CharacterPositions.begin();
2553 int row = 0;
2554 while (current != m_CharacterPositions.end())
2555 {
2556 if (m_iBufferPos >= current->m_ListStart &&
2557 m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
2558 break;
2559
2560 ++current;
2561 ++row;
2562 }
2563
2564 // If scrolling down
2565 if (-scroll + static_cast<float>(row + 1) * spacing + m_BufferZone * 2.f > m_CachedActualSize.GetHeight())
2566 {
2567 // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge.
2568 GetScrollBar(0).SetPos(static_cast<float>(row + 1) * spacing - m_CachedActualSize.GetHeight() + m_BufferZone * 2.f);
2569 }
2570 // If scrolling up
2571 else if (-scroll + (float)row * spacing < 0.f)
2572 {
2573 // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge.
2574 GetScrollBar(0).SetPos((float)row * spacing);
2575 }
2576 }
2577 else // autoscrolling left and right
2578 {
2579 // Get X position of position:
2580 if (m_CharacterPositions.empty())
2581 return;
2582
2583 float x_position = 0.f;
2584 float x_total = 0.f;
2585 if (!m_CharacterPositions.begin()->m_ListOfX.empty())
2586 {
2587
2588 // Get position of m_iBufferPos
2589 if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos &&
2590 m_iBufferPos > 0)
2591 x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1];
2592
2593 // Get complete length:
2594 x_total = m_CharacterPositions.begin()->m_ListOfX[m_CharacterPositions.begin()->m_ListOfX.size()-1];
2595 }
2596
2597 // Check if outside to the right
2598 if (x_position - m_HorizontalScroll + m_BufferZone * 2.f > m_CachedActualSize.GetWidth())
2599 m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f;
2600
2601 // Check if outside to the left
2602 if (x_position - m_HorizontalScroll < 0.f)
2603 m_HorizontalScroll = x_position;
2604
2605 // Check if the text doesn't even fill up to the right edge even though scrolling is done.
2606 if (m_HorizontalScroll != 0.f &&
2607 x_total - m_HorizontalScroll + m_BufferZone * 2.f < m_CachedActualSize.GetWidth())
2608 m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f;
2609
2610 // Now this is the fail-safe, if x_total isn't even the length of the control,
2611 // remove all scrolling
2612 if (x_total + m_BufferZone * 2.f < m_CachedActualSize.GetWidth())
2613 m_HorizontalScroll = 0.f;
2614 }
2615 }
2616Index: source/lib/lib_api.h
2617===================================================================
2618--- source/lib/lib_api.h (revision 23275)
2619+++ source/lib/lib_api.h (working copy)
2620@@ -1,56 +1,51 @@
2621 /* Copyright (C) 2010 Wildfire Games.
2622 *
2623 * Permission is hereby granted, free of charge, to any person obtaining
2624 * a copy of this software and associated documentation files (the
2625 * "Software"), to deal in the Software without restriction, including
2626 * without limitation the rights to use, copy, modify, merge, publish,
2627 * distribute, sublicense, and/or sell copies of the Software, and to
2628 * permit persons to whom the Software is furnished to do so, subject to
2629 * the following conditions:
2630 *
2631 * The above copyright notice and this permission notice shall be included
2632 * in all copies or substantial portions of the Software.
2633 *
2634 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2635 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2636 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2637 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2638 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2639 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2640 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2641 */
2642
2643 #ifndef INCLUDED_LIB_API
2644 #define INCLUDED_LIB_API
2645
2646 #include "lib/sysdep/compiler.h"
2647
2648 // note: EXTERN_C cannot be used because shared_ptr is often returned
2649 // by value, which requires C++ linkage.
2650
2651 #ifdef LIB_STATIC_LINK
2652 # define LIB_API
2653 #else
2654 # if MSC_VERSION
2655 # ifdef LIB_BUILD
2656 # define LIB_API __declspec(dllexport)
2657 # else
2658 # define LIB_API __declspec(dllimport)
2659-# ifdef NDEBUG
2660-# pragma comment(lib, "lowlevel.lib")
2661-# else
2662-# pragma comment(lib, "lowlevel_d.lib")
2663-# endif
2664 # endif
2665 # elif GCC_VERSION
2666 # ifdef LIB_BUILD
2667 # define LIB_API __attribute__ ((visibility("default")))
2668 # else
2669 # define LIB_API
2670 # endif
2671 # else
2672 # error "Don't know how to define LIB_API for this compiler"
2673 # endif
2674 #endif
2675
2676 #endif // #ifndef INCLUDED_LIB_API
2677Index: source/lib/res/graphics/cursor.cpp
2678===================================================================
2679--- source/lib/res/graphics/cursor.cpp (revision 23275)
2680+++ source/lib/res/graphics/cursor.cpp (working copy)
2681@@ -1,366 +1,356 @@
2682 /* Copyright (C) 2017 Wildfire Games.
2683 *
2684 * Permission is hereby granted, free of charge, to any person obtaining
2685 * a copy of this software and associated documentation files (the
2686 * "Software"), to deal in the Software without restriction, including
2687 * without limitation the rights to use, copy, modify, merge, publish,
2688 * distribute, sublicense, and/or sell copies of the Software, and to
2689 * permit persons to whom the Software is furnished to do so, subject to
2690 * the following conditions:
2691 *
2692 * The above copyright notice and this permission notice shall be included
2693 * in all copies or substantial portions of the Software.
2694 *
2695 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2696 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2697 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2698 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2699 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2700 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2701 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2702 */
2703
2704 /*
2705 * mouse cursors (either via OpenGL texture or hardware)
2706 */
2707
2708 #include "precompiled.h"
2709 #include "cursor.h"
2710
2711 #include <cstdio>
2712 #include <cstring>
2713 #include <sstream>
2714
2715 #include "lib/external_libraries/libsdl.h"
2716 #include "lib/ogl.h"
2717 #include "lib/res/h_mgr.h"
2718-#include "lib/sysdep/cursor.h"
2719 #include "ogl_tex.h"
2720
2721-// On Windows, allow runtime choice between system cursors and OpenGL
2722-// cursors (Windows = more responsive, OpenGL = more consistent with what
2723-// the game sees)
2724-#if OS_WIN || OS_UNIX
2725-# define ALLOW_SYS_CURSOR 1
2726-#else
2727-# define ALLOW_SYS_CURSOR 0
2728-#endif
2729-
2730 class SDLCursor
2731 {
2732 SDL_Surface* surface;
2733 SDL_Cursor* cursor;
2734
2735 public:
2736 Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale)
2737 {
2738 shared_ptr<u8> file; size_t fileSize;
2739 RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize));
2740
2741 Tex t;
2742 RETURN_STATUS_IF_ERR(t.decode(file, fileSize));
2743
2744 // convert to required BGRA format.
2745 const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT;
2746 RETURN_STATUS_IF_ERR(t.transform_to(flags));
2747 void* bgra_img = t.get_data();
2748 if(!bgra_img)
2749 WARN_RETURN(ERR::FAIL);
2750
2751 surface = SDL_CreateRGBSurfaceFrom(bgra_img, (int)t.m_Width, (int)t.m_Height, 32, (int)t.m_Width*4, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
2752 if(!surface)
2753 return ERR::FAIL;
2754 if(scale != 1.0)
2755 {
2756 SDL_Surface* scaled_surface = SDL_CreateRGBSurface(0, surface->w * scale, surface->h * scale, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
2757 if(!scaled_surface)
2758 return ERR::FAIL;
2759 if(SDL_BlitScaled(surface, NULL, scaled_surface, NULL))
2760 return ERR::FAIL;
2761 SDL_FreeSurface(surface);
2762 surface = scaled_surface;
2763 }
2764 cursor = SDL_CreateColorCursor(surface, hotspotx_, hotspoty_);
2765 if(!cursor)
2766 return ERR::FAIL;
2767
2768 return INFO::OK;
2769 }
2770
2771 void set()
2772 {
2773 SDL_SetCursor(cursor);
2774 }
2775
2776 void destroy()
2777 {
2778 SDL_FreeCursor(cursor);
2779 SDL_FreeSurface(surface);
2780 }
2781 };
2782
2783 // no init is necessary because this is stored in struct Cursor, which
2784 // is 0-initialized by h_mgr.
2785 class GLCursor
2786 {
2787 Handle ht;
2788
2789 GLint w, h;
2790 int hotspotx, hotspoty;
2791
2792 public:
2793 Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale)
2794 {
2795 ht = ogl_tex_load(vfs, pathname);
2796 RETURN_STATUS_IF_ERR(ht);
2797
2798 size_t width, height;
2799 (void)ogl_tex_get_size(ht, &width, &height, 0);
2800 w = (GLint)(width * scale);
2801 h = (GLint)(height * scale);
2802
2803 hotspotx = hotspotx_; hotspoty = hotspoty_;
2804
2805 (void)ogl_tex_set_filter(ht, GL_NEAREST);
2806 (void)ogl_tex_upload(ht);
2807 return INFO::OK;
2808 }
2809
2810 void destroy()
2811 {
2812 // note: we're stored in a resource => ht is initially 0 =>
2813 // this is safe, no need for an is_valid flag
2814 (void)ogl_tex_free(ht);
2815 }
2816
2817 void draw(int x, int y) const
2818 {
2819 #if CONFIG2_GLES
2820 UNUSED2(x); UNUSED2(y);
2821 #warning TODO: implement cursors for GLES
2822 #else
2823 (void)ogl_tex_bind(ht);
2824 glEnable(GL_TEXTURE_2D);
2825 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
2826 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2827 glEnable(GL_BLEND);
2828 glDisable(GL_DEPTH_TEST);
2829 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
2830
2831 glBegin(GL_QUADS);
2832 glTexCoord2i(1, 0); glVertex2i( x-hotspotx+w, y+hotspoty );
2833 glTexCoord2i(0, 0); glVertex2i( x-hotspotx, y+hotspoty );
2834 glTexCoord2i(0, 1); glVertex2i( x-hotspotx, y+hotspoty-h );
2835 glTexCoord2i(1, 1); glVertex2i( x-hotspotx+w, y+hotspoty-h );
2836 glEnd();
2837
2838 glDisable(GL_BLEND);
2839 glEnable(GL_DEPTH_TEST);
2840 #endif
2841 }
2842
2843 Status validate() const
2844 {
2845 const GLint A = 128; // no cursor is expected to get this big
2846 if(w > A || h > A || hotspotx > A || hotspoty > A)
2847 WARN_RETURN(ERR::_1);
2848 if(ht < 0)
2849 WARN_RETURN(ERR::_2);
2850 return INFO::OK;
2851 }
2852 };
2853
2854 enum CursorKind
2855 {
2856 CK_Default,
2857 CK_SDL,
2858 CK_OpenGL
2859 };
2860
2861 struct Cursor
2862 {
2863 double scale;
2864
2865 // require kind == CK_OpenGL after reload
2866 bool forceGL;
2867
2868 CursorKind kind;
2869
2870 // valid iff kind == CK_SDL
2871 SDLCursor sdl_cursor;
2872
2873 // valid iff kind == CK_OpenGL
2874 GLCursor gl_cursor;
2875 };
2876
2877 H_TYPE_DEFINE(Cursor);
2878
2879 static void Cursor_init(Cursor* c, va_list args)
2880 {
2881 c->scale = va_arg(args, double);
2882 c->forceGL = (va_arg(args, int) != 0);
2883 }
2884
2885 static void Cursor_dtor(Cursor* c)
2886 {
2887 switch(c->kind)
2888 {
2889 case CK_Default:
2890 break; // nothing to do
2891
2892 case CK_SDL:
2893 c->sdl_cursor.destroy();
2894 break;
2895
2896 case CK_OpenGL:
2897 c->gl_cursor.destroy();
2898 break;
2899
2900 default:
2901 DEBUG_WARN_ERR(ERR::LOGIC);
2902 break;
2903 }
2904 }
2905
2906 static Status Cursor_reload(Cursor* c, const PIVFS& vfs, const VfsPath& name, Handle)
2907 {
2908 const VfsPath pathname(VfsPath(L"art/textures/cursors") / name);
2909
2910 // read pixel offset of the cursor's hotspot [the bit of it that's
2911 // drawn at (g_mouse_x,g_mouse_y)] from file.
2912 int hotspotx = 0, hotspoty = 0;
2913 {
2914 const VfsPath pathnameHotspot = pathname.ChangeExtension(L".txt");
2915 shared_ptr<u8> buf; size_t size;
2916 RETURN_STATUS_IF_ERR(vfs->LoadFile(pathnameHotspot, buf, size));
2917 std::wstringstream s(std::wstring((const wchar_t*)buf.get(), size));
2918 s >> hotspotx >> hotspoty;
2919 }
2920
2921 const VfsPath pathnameImage = pathname.ChangeExtension(L".png");
2922
2923 // try loading as SDL2 cursor
2924 if(!c->forceGL && c->sdl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK)
2925 c->kind = CK_SDL;
2926 // fall back to GLCursor (system cursor code is disabled or failed)
2927 else if(c->gl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK)
2928 c->kind = CK_OpenGL;
2929 // everything failed, leave cursor unchanged
2930 else
2931 c->kind = CK_Default;
2932
2933 return INFO::OK;
2934 }
2935
2936 static Status Cursor_validate(const Cursor* c)
2937 {
2938 switch(c->kind)
2939 {
2940 case CK_Default:
2941 break; // nothing to do
2942
2943 case CK_SDL:
2944 break; // nothing to do
2945
2946 case CK_OpenGL:
2947 RETURN_STATUS_IF_ERR(c->gl_cursor.validate());
2948 break;
2949
2950 default:
2951 WARN_RETURN(ERR::_2);
2952 break;
2953 }
2954
2955 return INFO::OK;
2956 }
2957
2958 static Status Cursor_to_string(const Cursor* c, wchar_t* buf)
2959 {
2960 const wchar_t* type;
2961 switch(c->kind)
2962 {
2963 case CK_Default:
2964 type = L"default";
2965 break;
2966
2967 case CK_SDL:
2968 type = L"sdl";
2969 break;
2970
2971 case CK_OpenGL:
2972 type = L"gl";
2973 break;
2974
2975 default:
2976 DEBUG_WARN_ERR(ERR::LOGIC);
2977 type = L"?";
2978 break;
2979 }
2980
2981 swprintf_s(buf, H_STRING_LEN, L"cursor (%ls)", type);
2982 return INFO::OK;
2983 }
2984
2985
2986 // note: these standard resource interface functions are not exposed to the
2987-// caller. all we need here is storage for the sys_cursor / GLCursor and
2988+// caller. all we need here is storage for the GLCursor and
2989 // a name -> data lookup mechanism, both provided by h_mgr.
2990 // in other words, we continually create/free the cursor resource in
2991 // cursor_draw and trust h_mgr's caching to absorb it.
2992
2993 static Handle cursor_load(const PIVFS& vfs, const VfsPath& name, double scale, bool forceGL)
2994 {
2995 return h_alloc(H_Cursor, vfs, name, 0, scale, (int)forceGL);
2996 }
2997
2998 void cursor_shutdown()
2999 {
3000 h_mgr_free_type(H_Cursor);
3001 }
3002
3003 static Status cursor_free(Handle& h)
3004 {
3005 return h_free(h, H_Cursor);
3006 }
3007
3008
3009 Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL)
3010 {
3011 // hide the cursor
3012 if(!name)
3013 {
3014 SDL_ShowCursor(SDL_DISABLE);
3015 return INFO::OK;
3016 }
3017
3018 Handle hc = cursor_load(vfs, name, scale, forceGL);
3019 // TODO: if forceGL changes at runtime after a cursor is first created,
3020 // we might reuse a cached version of the cursor with the old forceGL flag
3021
3022 RETURN_STATUS_IF_ERR(hc); // silently ignore failures
3023
3024 H_DEREF(hc, Cursor, c);
3025
3026 switch(c->kind)
3027 {
3028 case CK_Default:
3029 break;
3030
3031 case CK_SDL:
3032 c->sdl_cursor.set();
3033 SDL_ShowCursor(SDL_ENABLE);
3034 break;
3035
3036 case CK_OpenGL:
3037 c->gl_cursor.draw(x, y);
3038 SDL_ShowCursor(SDL_DISABLE);
3039 break;
3040
3041 default:
3042 DEBUG_WARN_ERR(ERR::LOGIC);
3043 break;
3044 }
3045
3046 (void)cursor_free(hc);
3047 return INFO::OK;
3048 }
3049Index: source/lib/sysdep/clipboard.h
3050===================================================================
3051--- source/lib/sysdep/clipboard.h (revision 23275)
3052+++ source/lib/sysdep/clipboard.h (nonexistent)
3053@@ -1,38 +0,0 @@
3054-/* Copyright (C) 2011 Wildfire Games.
3055- *
3056- * Permission is hereby granted, free of charge, to any person obtaining
3057- * a copy of this software and associated documentation files (the
3058- * "Software"), to deal in the Software without restriction, including
3059- * without limitation the rights to use, copy, modify, merge, publish,
3060- * distribute, sublicense, and/or sell copies of the Software, and to
3061- * permit persons to whom the Software is furnished to do so, subject to
3062- * the following conditions:
3063- *
3064- * The above copyright notice and this permission notice shall be included
3065- * in all copies or substantial portions of the Software.
3066- *
3067- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3068- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3069- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3070- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3071- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3072- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3073- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3074- */
3075-
3076-#ifndef INCLUDED_SYSDEP_CLIPBOARD
3077-#define INCLUDED_SYSDEP_CLIPBOARD
3078-
3079-// "copy" text into the clipboard. replaces previous contents.
3080-extern Status sys_clipboard_set(const wchar_t* text);
3081-
3082-// allow "pasting" from clipboard.
3083-// @return current clipboard text or 0 if not representable as text.
3084-// callers are responsible for passing this pointer to sys_clipboard_free.
3085-extern wchar_t* sys_clipboard_get();
3086-
3087-// free memory returned by sys_clipboard_get.
3088-// @param copy is ignored if 0.
3089-extern Status sys_clipboard_free(wchar_t* copy);
3090-
3091-#endif // #ifndef INCLUDED_SYSDEP_CLIPBOARD
3092
3093Property changes on: source/lib/sysdep/clipboard.h
3094___________________________________________________________________
3095Deleted: svn:eol-style
3096## -1 +0,0 ##
3097-native
3098\ No newline at end of property
3099Index: source/lib/sysdep/cursor.h
3100===================================================================
3101--- source/lib/sysdep/cursor.h (revision 23275)
3102+++ source/lib/sysdep/cursor.h (nonexistent)
3103@@ -1,74 +0,0 @@
3104-/* Copyright (C) 2010 Wildfire Games.
3105- *
3106- * Permission is hereby granted, free of charge, to any person obtaining
3107- * a copy of this software and associated documentation files (the
3108- * "Software"), to deal in the Software without restriction, including
3109- * without limitation the rights to use, copy, modify, merge, publish,
3110- * distribute, sublicense, and/or sell copies of the Software, and to
3111- * permit persons to whom the Software is furnished to do so, subject to
3112- * the following conditions:
3113- *
3114- * The above copyright notice and this permission notice shall be included
3115- * in all copies or substantial portions of the Software.
3116- *
3117- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3118- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3119- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3120- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3121- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3122- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3123- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3124- */
3125-
3126-/*
3127- * mouse cursor
3128- */
3129-
3130-#ifndef INCLUDED_SYSDEP_CURSOR
3131-#define INCLUDED_SYSDEP_CURSOR
3132-
3133-typedef void* sys_cursor;
3134-
3135-/**
3136- * Create a cursor from the given color image.
3137- *
3138- * @param w,h Image dimensions [pixels]. the maximum value is
3139- * implementation-defined; 32x32 is typical and safe.
3140- * @param bgra_img cursor image (BGRA format, bottom-up).
3141- * It is copied and can be freed after this call returns.
3142- * @param hx,hy 'hotspot', i.e. offset from the upper-left corner to the
3143- * position where mouse clicks are registered.
3144- * @param cursor Is 0 if the return code indicates failure, otherwise
3145- * a valid cursor that must be sys_cursor_free-ed when no longer needed.
3146- **/
3147-extern Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor);
3148-
3149-/**
3150- * Create a transparent cursor (used to hide the system cursor).
3151- *
3152- * @param cursor is 0 if the return code indicates failure, otherwise
3153- * a valid cursor that must be sys_cursor_free-ed when no longer needed.
3154- **/
3155-extern Status sys_cursor_create_empty(sys_cursor* cursor);
3156-
3157-/**
3158- * override the current system cursor.
3159- *
3160- * @param cursor can be 0 to restore the default.
3161- **/
3162-extern Status sys_cursor_set(sys_cursor cursor);
3163-
3164-/**
3165- * destroy the indicated cursor and frees its resources.
3166- *
3167- * @param cursor if currently in use, the default cursor is restored first.
3168- **/
3169-extern Status sys_cursor_free(sys_cursor cursor);
3170-
3171-/**
3172- * reset any cached cursor data.
3173- * on some systems, this is needed when resetting the SDL video subsystem.
3174- **/
3175-extern Status sys_cursor_reset();
3176-
3177-#endif // #ifndef INCLUDED_SYSDEP_CURSOR
3178
3179Property changes on: source/lib/sysdep/cursor.h
3180___________________________________________________________________
3181Deleted: svn:eol-style
3182## -1 +0,0 ##
3183-native
3184\ No newline at end of property
3185Index: source/lib/sysdep/gfx.cpp
3186===================================================================
3187--- source/lib/sysdep/gfx.cpp (revision 23275)
3188+++ source/lib/sysdep/gfx.cpp (working copy)
3189@@ -1,101 +1,92 @@
3190 /* Copyright (C) 2015 Wildfire Games.
3191 *
3192 * Permission is hereby granted, free of charge, to any person obtaining
3193 * a copy of this software and associated documentation files (the
3194 * "Software"), to deal in the Software without restriction, including
3195 * without limitation the rights to use, copy, modify, merge, publish,
3196 * distribute, sublicense, and/or sell copies of the Software, and to
3197 * permit persons to whom the Software is furnished to do so, subject to
3198 * the following conditions:
3199 *
3200 * The above copyright notice and this permission notice shall be included
3201 * in all copies or substantial portions of the Software.
3202 *
3203 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3204 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3205 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3206 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3207 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3208 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3209 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3210 */
3211
3212 /*
3213 * graphics card detection.
3214 */
3215
3216 #include "precompiled.h"
3217 #include "lib/sysdep/gfx.h"
3218
3219 #include "lib/external_libraries/libsdl.h"
3220 #include "lib/ogl.h"
3221
3222 #if OS_WIN
3223 # include "lib/sysdep/os/win/wgfx.h"
3224 #endif
3225
3226
3227 namespace gfx {
3228
3229 std::wstring CardName()
3230 {
3231 // GL_VENDOR+GL_RENDERER are good enough here, so we don't use WMI to detect the cards.
3232 // On top of that WMI can cause crashes with Nvidia Optimus and some netbooks
3233 // see http://trac.wildfiregames.com/ticket/1952
3234 // http://trac.wildfiregames.com/ticket/1575
3235 wchar_t cardName[128];
3236 const char* vendor = (const char*)glGetString(GL_VENDOR);
3237 const char* renderer = (const char*)glGetString(GL_RENDERER);
3238 // (happens if called before ogl_Init or between glBegin and glEnd.)
3239 if(!vendor || !renderer)
3240 return L"";
3241 swprintf_s(cardName, ARRAY_SIZE(cardName), L"%hs %hs", vendor, renderer);
3242
3243 // remove crap from vendor names. (don't dare touch the model name -
3244 // it's too risky, there are too many different strings)
3245 #define SHORTEN(what, charsToKeep)\
3246 if(!wcsncmp(cardName, what, ARRAY_SIZE(what)-1))\
3247 memmove(cardName+charsToKeep, cardName+ARRAY_SIZE(what)-1, (wcslen(cardName)-(ARRAY_SIZE(what)-1)+1)*sizeof(wchar_t));
3248 SHORTEN(L"ATI Technologies Inc.", 3);
3249 SHORTEN(L"NVIDIA Corporation", 6);
3250 SHORTEN(L"S3 Graphics", 2); // returned by EnumDisplayDevices
3251 SHORTEN(L"S3 Graphics, Incorporated", 2); // returned by GL_VENDOR
3252 #undef SHORTEN
3253
3254 return cardName;
3255 }
3256
3257
3258 std::wstring DriverInfo()
3259 {
3260 std::wstring driverInfo;
3261 #if OS_WIN
3262 driverInfo = wgfx_DriverInfo();
3263 if(driverInfo.empty())
3264 #endif
3265 {
3266 const char* version = (const char*)glGetString(GL_VERSION);
3267 if(version)
3268 {
3269 // add "OpenGL" to differentiate this from the real driver version
3270 // (returned by platform-specific detect routines).
3271 driverInfo = std::wstring(L"OpenGL ") + std::wstring(version, version+strlen(version));
3272 }
3273 }
3274
3275 if(driverInfo.empty())
3276 return L"(unknown)";
3277 return driverInfo;
3278 }
3279
3280
3281-size_t MemorySizeMiB()
3282-{
3283- // TODO: not implemented, SDL_GetVideoInfo only works on some platforms in SDL 1.2
3284- // and no replacement is available in SDL2, and it can crash with Nvidia Optimus
3285- // see http://trac.wildfiregames.com/ticket/2145
3286- debug_warn(L"MemorySizeMiB not implemented");
3287- return 0;
3288-}
3289-
3290 } // namespace gfx
3291Index: source/lib/sysdep/gfx.h
3292===================================================================
3293--- source/lib/sysdep/gfx.h (revision 23275)
3294+++ source/lib/sysdep/gfx.h (working copy)
3295@@ -1,70 +1,46 @@
3296 /* Copyright (C) 2013 Wildfire Games.
3297 *
3298 * Permission is hereby granted, free of charge, to any person obtaining
3299 * a copy of this software and associated documentation files (the
3300 * "Software"), to deal in the Software without restriction, including
3301 * without limitation the rights to use, copy, modify, merge, publish,
3302 * distribute, sublicense, and/or sell copies of the Software, and to
3303 * permit persons to whom the Software is furnished to do so, subject to
3304 * the following conditions:
3305 *
3306 * The above copyright notice and this permission notice shall be included
3307 * in all copies or substantial portions of the Software.
3308 *
3309 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3310 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3311 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3312 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3313 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3314 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3315 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3316 */
3317
3318 /*
3319 * graphics card detection.
3320 */
3321
3322 #ifndef INCLUDED_GFX
3323 #define INCLUDED_GFX
3324
3325 namespace gfx {
3326
3327 /**
3328 * @return description of graphics card,
3329 * or L"" if unknown.
3330 **/
3331 LIB_API std::wstring CardName();
3332
3333 /**
3334 * @return string describing the graphics driver and its version,
3335 * or L"" if unknown.
3336 **/
3337 LIB_API std::wstring DriverInfo();
3338
3339-/**
3340- * not implemented
3341- **/
3342-LIB_API size_t MemorySizeMiB();
3343-
3344-/**
3345- * (useful for choosing a new video mode)
3346- *
3347- * @param xres, yres (optional out) resolution [pixels]
3348- * @param bpp (optional out) bits per pixel
3349- * @param freq (optional out) vertical refresh rate [Hz]
3350- * @return Status (if negative, outputs were left unchanged)
3351- **/
3352-LIB_API Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq);
3353-
3354-/**
3355- * (useful for determining aspect ratio)
3356- *
3357- * @param width_mm (out) screen width [mm]
3358- * @param height_mm (out) screen height [mm]
3359- * @return Status (if if negative, outputs were left unchanged)
3360- **/
3361-LIB_API Status GetMonitorSize(int& width_mm, int& height_mm);
3362-
3363 } // namespace gfx
3364
3365 #endif // #ifndef INCLUDED_GFX
3366Index: source/lib/sysdep/os/android/android.cpp
3367===================================================================
3368--- source/lib/sysdep/os/android/android.cpp (revision 23275)
3369+++ source/lib/sysdep/os/android/android.cpp (nonexistent)
3370@@ -1,120 +0,0 @@
3371-/* Copyright (C) 2012 Wildfire Games.
3372- *
3373- * Permission is hereby granted, free of charge, to any person obtaining
3374- * a copy of this software and associated documentation files (the
3375- * "Software"), to deal in the Software without restriction, including
3376- * without limitation the rights to use, copy, modify, merge, publish,
3377- * distribute, sublicense, and/or sell copies of the Software, and to
3378- * permit persons to whom the Software is furnished to do so, subject to
3379- * the following conditions:
3380- *
3381- * The above copyright notice and this permission notice shall be included
3382- * in all copies or substantial portions of the Software.
3383- *
3384- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3385- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3386- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3387- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3388- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3389- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3390- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3391- */
3392-
3393-#include "precompiled.h"
3394-
3395-#include "lib/sysdep/sysdep.h"
3396-#include "lib/sysdep/cursor.h"
3397-
3398-#include "lib/external_libraries/libsdl.h"
3399-
3400-Status sys_clipboard_set(const wchar_t* UNUSED(text))
3401-{
3402- return INFO::OK;
3403-}
3404-
3405-wchar_t* sys_clipboard_get()
3406-{
3407- return NULL;
3408-}
3409-
3410-Status sys_clipboard_free(wchar_t* UNUSED(copy))
3411-{
3412- return INFO::OK;
3413-}
3414-
3415-namespace gfx {
3416-
3417-Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
3418-{
3419-#warning TODO: implement gfx::GetVideoMode properly for Android
3420-
3421- if(xres)
3422- *xres = 800;
3423-
3424- if(yres)
3425- *yres = 480;
3426-
3427- if(bpp)
3428- *bpp = 32;
3429-
3430- if(freq)
3431- *freq = 0;
3432-
3433- return INFO::OK;
3434-}
3435-
3436-}
3437-
3438-// stub implementation of sys_cursor* functions
3439-
3440-// note: do not return ERR_NOT_IMPLEMENTED or similar because that
3441-// would result in WARN_ERRs.
3442-Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
3443-{
3444- UNUSED2(w);
3445- UNUSED2(h);
3446- UNUSED2(hx);
3447- UNUSED2(hy);
3448- UNUSED2(bgra_img);
3449-
3450- *cursor = 0;
3451- return INFO::OK;
3452-}
3453-
3454-// returns a dummy value representing an empty cursor
3455-Status sys_cursor_create_empty(sys_cursor* cursor)
3456-{
3457- *cursor = (void*)1; // any non-zero value, since the cursor NULL has special meaning
3458- return INFO::OK;
3459-}
3460-
3461-// replaces the current system cursor with the one indicated. need only be
3462-// called once per cursor; pass 0 to restore the default.
3463-Status sys_cursor_set(sys_cursor cursor)
3464-{
3465- if (cursor) // dummy empty cursor
3466- SDL_ShowCursor(SDL_DISABLE);
3467- else // restore default cursor
3468- SDL_ShowCursor(SDL_ENABLE);
3469-
3470- return INFO::OK;
3471-}
3472-
3473-// destroys the indicated cursor and frees its resources. if it is
3474-// currently the system cursor, the default cursor is restored first.
3475-Status sys_cursor_free(sys_cursor cursor)
3476-{
3477- // bail now to prevent potential confusion below; there's nothing to do.
3478- if(!cursor)
3479- return INFO::OK;
3480-
3481- SDL_ShowCursor(SDL_ENABLE);
3482-
3483- return INFO::OK;
3484-}
3485-
3486-Status sys_cursor_reset()
3487-{
3488- return INFO::OK;
3489-}
3490-
3491
3492Property changes on: source/lib/sysdep/os/android/android.cpp
3493___________________________________________________________________
3494Deleted: svn:eol-style
3495## -1 +0,0 ##
3496-native
3497\ No newline at end of property
3498Index: source/lib/sysdep/os/osx/osx.cpp
3499===================================================================
3500--- source/lib/sysdep/os/osx/osx.cpp (revision 23275)
3501+++ source/lib/sysdep/os/osx/osx.cpp (working copy)
3502@@ -1,186 +1,66 @@
3503 /* Copyright (C) 2015 Wildfire Games.
3504 *
3505 * Permission is hereby granted, free of charge, to any person obtaining
3506 * a copy of this software and associated documentation files (the
3507 * "Software"), to deal in the Software without restriction, including
3508 * without limitation the rights to use, copy, modify, merge, publish,
3509 * distribute, sublicense, and/or sell copies of the Software, and to
3510 * permit persons to whom the Software is furnished to do so, subject to
3511 * the following conditions:
3512 *
3513 * The above copyright notice and this permission notice shall be included
3514 * in all copies or substantial portions of the Software.
3515 *
3516 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3517 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3518 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3519 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3520 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3521 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3522 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3523 */
3524
3525 #include "precompiled.h"
3526
3527 #include "lib/lib.h"
3528 #include "lib/sysdep/sysdep.h"
3529 #include "lib/sysdep/gfx.h"
3530 #include "lib/utf8.h"
3531 #include "osx_bundle.h"
3532-#include "osx_pasteboard.h"
3533
3534 #include <ApplicationServices/ApplicationServices.h>
3535 #include <AvailabilityMacros.h> // MAC_OS_X_VERSION_MIN_REQUIRED
3536 #include <CoreFoundation/CoreFoundation.h>
3537 #include <mach-o/dyld.h> // _NSGetExecutablePath
3538
3539 // Ignore deprecation warnings for 10.5 backwards compatibility
3540 #pragma GCC diagnostic push
3541 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
3542
3543-Status sys_clipboard_set(const wchar_t* text)
3544-{
3545- Status ret = INFO::OK;
3546-
3547- std::string str = utf8_from_wstring(text);
3548- bool ok = osx_SendStringToPasteboard(str);
3549- if (!ok)
3550- ret = ERR::FAIL;
3551- return ret;
3552-}
3553-
3554-wchar_t* sys_clipboard_get()
3555-{
3556- wchar_t* ret = NULL;
3557- std::string str;
3558- bool ok = osx_GetStringFromPasteboard(str);
3559- if (ok)
3560- {
3561- // TODO: this is yucky, why are we passing around wchar_t*?
3562- std::wstring wstr = wstring_from_utf8(str);
3563- size_t len = wcslen(wstr.c_str());
3564- ret = (wchar_t*)malloc((len+1)*sizeof(wchar_t));
3565- std::copy(wstr.c_str(), wstr.c_str()+len, ret);
3566- ret[len] = 0;
3567- }
3568-
3569- return ret;
3570-}
3571-
3572-Status sys_clipboard_free(wchar_t* copy)
3573-{
3574- free(copy);
3575- return INFO::OK;
3576-}
3577-
3578-
3579-namespace gfx {
3580-
3581-Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
3582-{
3583- if(xres)
3584- *xres = (int)CGDisplayPixelsWide(kCGDirectMainDisplay);
3585-
3586- if(yres)
3587- *yres = (int)CGDisplayPixelsHigh(kCGDirectMainDisplay);
3588-
3589- if(bpp)
3590- {
3591-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3592- // CGDisplayBitsPerPixel was deprecated in OS X 10.6
3593- if (CGDisplayCopyDisplayMode != NULL)
3594- {
3595- CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay);
3596- CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(currentMode);
3597- if (CFStringCompare(pixelEncoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
3598- *bpp = 32;
3599- else if (CFStringCompare(pixelEncoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
3600- *bpp = 16;
3601- else if (CFStringCompare(pixelEncoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
3602- *bpp = 8;
3603- else // error
3604- *bpp = 0;
3605-
3606- // We're responsible for this
3607- CFRelease(pixelEncoding);
3608- CGDisplayModeRelease(currentMode);
3609- }
3610- else
3611- {
3612-#endif // fallback to 10.5 API
3613- CFDictionaryRef currentMode = CGDisplayCurrentMode(kCGDirectMainDisplay);
3614- CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(currentMode, kCGDisplayBitsPerPixel);
3615- CFNumberGetValue(num, kCFNumberIntType, bpp);
3616-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3617- }
3618-#endif
3619- }
3620-
3621- if(freq)
3622- {
3623-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3624- if (CGDisplayCopyDisplayMode != NULL)
3625- {
3626- CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay);
3627- *freq = (int)CGDisplayModeGetRefreshRate(currentMode);
3628-
3629- // We're responsible for this
3630- CGDisplayModeRelease(currentMode);
3631- }
3632- else
3633- {
3634-#endif // fallback to 10.5 API
3635- CFDictionaryRef currentMode = CGDisplayCurrentMode(kCGDirectMainDisplay);
3636- CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(currentMode, kCGDisplayRefreshRate);
3637- CFNumberGetValue(num, kCFNumberIntType, freq);
3638-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3639- }
3640-#endif
3641- }
3642-
3643- return INFO::OK;
3644-}
3645-
3646-Status GetMonitorSize(int& width_mm, int& height_mm)
3647-{
3648- CGSize screenSize = CGDisplayScreenSize(kCGDirectMainDisplay);
3649-
3650- if (screenSize.width == 0 || screenSize.height == 0)
3651- return ERR::FAIL;
3652-
3653- width_mm = screenSize.width;
3654- height_mm = screenSize.height;
3655-
3656- return INFO::OK;
3657-}
3658-
3659-} // namespace gfx
3660-
3661-
3662 OsPath sys_ExecutablePathname()
3663 {
3664 OsPath path;
3665
3666 // On OS X we might be a bundle, return the bundle path as the executable name,
3667 // i.e. /path/to/0ad.app instead of /path/to/0ad.app/Contents/MacOS/pyrogenesis
3668 if (osx_IsAppBundleValid())
3669 {
3670 path = osx_GetBundlePath();
3671 ENSURE(!path.empty());
3672 }
3673 else
3674 {
3675 char temp[PATH_MAX];
3676 u32 size = PATH_MAX;
3677 if (_NSGetExecutablePath(temp, &size) == 0)
3678 {
3679 char name[PATH_MAX];
3680 realpath(temp, name);
3681 path = OsPath(name);
3682 }
3683 }
3684
3685 return path;
3686 }
3687
3688 #pragma GCC diagnostic pop // restore user flags
3689Index: source/lib/sysdep/os/osx/osx_pasteboard.h
3690===================================================================
3691--- source/lib/sysdep/os/osx/osx_pasteboard.h (revision 23275)
3692+++ source/lib/sysdep/os/osx/osx_pasteboard.h (nonexistent)
3693@@ -1,47 +0,0 @@
3694-/* Copyright (C) 2012 Wildfire Games.
3695- *
3696- * Permission is hereby granted, free of charge, to any person obtaining
3697- * a copy of this software and associated documentation files (the
3698- * "Software"), to deal in the Software without restriction, including
3699- * without limitation the rights to use, copy, modify, merge, publish,
3700- * distribute, sublicense, and/or sell copies of the Software, and to
3701- * permit persons to whom the Software is furnished to do so, subject to
3702- * the following conditions:
3703- *
3704- * The above copyright notice and this permission notice shall be included
3705- * in all copies or substantial portions of the Software.
3706- *
3707- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3708- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3709- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3710- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3711- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3712- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3713- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3714- */
3715-
3716-#ifndef OSX_PASTEBOARD_H
3717-#define OSX_PASTEBOARD_H
3718-
3719-/**
3720- * @file
3721- * C++ interface to Cocoa implementation for pasteboards
3722- */
3723-
3724-/**
3725- * Get a string from the pasteboard
3726- *
3727- * @param[out] out pasteboard string in UTF-8 encoding, if found
3728- * @return true if string was found on pasteboard and successfully retrieved, false otherwise
3729- */
3730-bool osx_GetStringFromPasteboard(std::string& out);
3731-
3732-/**
3733- * Store a string on the pasteboard
3734- *
3735- * @param[in] string string to store in UTF-8 encoding
3736- * @return true if string was successfully sent to pasteboard, false on error
3737- */
3738-bool osx_SendStringToPasteboard(const std::string& string);
3739-
3740-#endif // OSX_PASTEBOARD_H
3741
3742Property changes on: source/lib/sysdep/os/osx/osx_pasteboard.h
3743___________________________________________________________________
3744Deleted: svn:eol-style
3745## -1 +0,0 ##
3746-native
3747\ No newline at end of property
3748Index: source/lib/sysdep/os/osx/osx_pasteboard.mm
3749===================================================================
3750--- source/lib/sysdep/os/osx/osx_pasteboard.mm (revision 23275)
3751+++ source/lib/sysdep/os/osx/osx_pasteboard.mm (nonexistent)
3752@@ -1,95 +0,0 @@
3753-/* Copyright (C) 2013 Wildfire Games.
3754- *
3755- * Permission is hereby granted, free of charge, to any person obtaining
3756- * a copy of this software and associated documentation files (the
3757- * "Software"), to deal in the Software without restriction, including
3758- * without limitation the rights to use, copy, modify, merge, publish,
3759- * distribute, sublicense, and/or sell copies of the Software, and to
3760- * permit persons to whom the Software is furnished to do so, subject to
3761- * the following conditions:
3762- *
3763- * The above copyright notice and this permission notice shall be included
3764- * in all copies or substantial portions of the Software.
3765- *
3766- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3767- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3768- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3769- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3770- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3771- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3772- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3773- */
3774-
3775-#import <AppKit/AppKit.h>
3776-#import <AvailabilityMacros.h> // MAC_OS_X_VERSION_MIN_REQUIRED
3777-#import <Foundation/Foundation.h>
3778-#import <string>
3779-
3780-#import "osx_pasteboard.h"
3781-
3782-bool osx_GetStringFromPasteboard(std::string& out)
3783-{
3784- NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
3785- NSString* string = nil;
3786-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3787- // As of 10.6, pasteboards can hold multiple items
3788- if ([pasteboard respondsToSelector: @selector(readObjectsForClasses:)])
3789- {
3790- NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil];
3791- NSDictionary* options = [NSDictionary dictionary];
3792- NSArray* copiedItems = [pasteboard readObjectsForClasses:classes options:options];
3793- // We only need to support a single item, so grab the first string
3794- if (copiedItems != nil && [copiedItems count] > 0)
3795- string = [copiedItems objectAtIndex:0];
3796- else
3797- return false; // No strings found on pasteboard
3798- }
3799- else
3800- {
3801-#endif // fallback to 10.5 API
3802- // Verify that there is a string available for us
3803- NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
3804- if ([pasteboard availableTypeFromArray:types] != nil)
3805- string = [pasteboard stringForType:NSStringPboardType];
3806- else
3807- return false; // No strings found on pasteboard
3808-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3809- }
3810-#endif
3811-
3812- if (string != nil)
3813- out = std::string([string UTF8String]);
3814- else
3815- return false; // fail
3816-
3817- return true; // success
3818-}
3819-
3820-bool osx_SendStringToPasteboard(const std::string& string)
3821-{
3822- // We're only working with strings, so we don't need to lazily write
3823- // anything (otherwise we'd need to set up an owner and data provider)
3824- NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
3825- NSString* type;
3826-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3827- if ([pasteboard respondsToSelector: @selector(clearContents)])
3828- {
3829- type = NSPasteboardTypeString;
3830- [pasteboard clearContents];
3831- }
3832- else
3833- {
3834-#endif // fallback to 10.5 API
3835- type = NSStringPboardType;
3836- NSArray* types = [NSArray arrayWithObjects: type, nil];
3837- // Roughly equivalent to clearContents followed by addTypes:owner
3838- [pasteboard declareTypes:types owner:nil];
3839-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
3840-
3841- }
3842-#endif
3843-
3844- // May raise a NSPasteboardCommunicationException
3845- BOOL ok = [pasteboard setString:[NSString stringWithUTF8String:string.c_str()] forType:type];
3846- return ok == YES;
3847-}
3848
3849Property changes on: source/lib/sysdep/os/osx/osx_pasteboard.mm
3850___________________________________________________________________
3851Deleted: svn:eol-style
3852## -1 +0,0 ##
3853-native
3854\ No newline at end of property
3855Index: source/lib/sysdep/os/osx/osx_sys_cursor.mm
3856===================================================================
3857--- source/lib/sysdep/os/osx/osx_sys_cursor.mm (revision 23275)
3858+++ source/lib/sysdep/os/osx/osx_sys_cursor.mm (nonexistent)
3859@@ -1,104 +0,0 @@
3860-/* Copyright (C) 2012 Wildfire Games.
3861- *
3862- * Permission is hereby granted, free of charge, to any person obtaining
3863- * a copy of this software and associated documentation files (the
3864- * "Software"), to deal in the Software without restriction, including
3865- * without limitation the rights to use, copy, modify, merge, publish,
3866- * distribute, sublicense, and/or sell copies of the Software, and to
3867- * permit persons to whom the Software is furnished to do so, subject to
3868- * the following conditions:
3869- *
3870- * The above copyright notice and this permission notice shall be included
3871- * in all copies or substantial portions of the Software.
3872- *
3873- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3874- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3875- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3876- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3877- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3878- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3879- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3880- */
3881-
3882-#import "precompiled.h"
3883-#import "lib/sysdep/cursor.h"
3884-
3885-#import <AppKit/NSCursor.h>
3886-#import <AppKit/NSImage.h>
3887-#import <ApplicationServices/ApplicationServices.h>
3888-
3889-//TODO: make sure these are threadsafe
3890-Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
3891-{
3892- NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc]
3893- initWithBitmapDataPlanes:0 pixelsWide:w pixelsHigh:h
3894- bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
3895- colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:w*4 bitsPerPixel:0];
3896- if (!bitmap)
3897- {
3898- debug_printf("sys_cursor_create: Error creating NSBitmapImageRep!\n");
3899- return ERR::FAIL;
3900- }
3901-
3902- u8* planes[5];
3903- [bitmap getBitmapDataPlanes:planes];
3904- const u8* bgra = static_cast<const u8*>(bgra_img);
3905- u8* dst = planes[0];
3906- for (int i = 0; i < w*h*4; i += 4)
3907- {
3908- dst[i] = bgra[i+2];
3909- dst[i+1] = bgra[i+1];
3910- dst[i+2] = bgra[i];
3911- dst[i+3] = bgra[i+3];
3912- }
3913-
3914- NSImage* image = [[NSImage alloc] init];
3915- if (!image)
3916- {
3917- [bitmap release];
3918- debug_printf("sys_cursor_create: Error creating NSImage!\n");
3919- return ERR::FAIL;
3920- }
3921-
3922- [image addRepresentation:bitmap];
3923- [bitmap release];
3924- NSCursor* impl = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(hx, hy)];
3925- [image release];
3926-
3927- if (!impl)
3928- {
3929- debug_printf("sys_cursor_create: Error creating NSCursor!\n");
3930- return ERR::FAIL;
3931- }
3932-
3933- *cursor = static_cast<sys_cursor>(impl);
3934-
3935- return INFO::OK;
3936-}
3937-
3938-Status sys_cursor_free(sys_cursor cursor)
3939-{
3940- NSCursor* impl = static_cast<NSCursor*>(cursor);
3941- [impl release];
3942- return INFO::OK;
3943-}
3944-
3945-Status sys_cursor_create_empty(sys_cursor* cursor)
3946-{
3947- static u8 empty_bgra[] = {0, 0, 0, 0};
3948- sys_cursor_create(1, 1, reinterpret_cast<void*>(empty_bgra), 0, 0, cursor);
3949- return INFO::OK;
3950-}
3951-
3952-Status sys_cursor_set(sys_cursor cursor)
3953-{
3954- NSCursor* impl = static_cast<NSCursor*>(cursor);
3955- [impl set];
3956- return INFO::OK;
3957-}
3958-
3959-Status sys_cursor_reset()
3960-{
3961- return INFO::OK;
3962-}
3963-
3964
3965Property changes on: source/lib/sysdep/os/osx/osx_sys_cursor.mm
3966___________________________________________________________________
3967Deleted: svn:eol-style
3968## -1 +0,0 ##
3969-native
3970\ No newline at end of property
3971Index: source/lib/sysdep/os/unix/unix_executable_pathname.cpp
3972===================================================================
3973--- source/lib/sysdep/os/unix/unix_executable_pathname.cpp (revision 23275)
3974+++ source/lib/sysdep/os/unix/unix_executable_pathname.cpp (working copy)
3975@@ -1,73 +1,72 @@
3976 /* Copyright (C) 2014 Wildfire Games.
3977 *
3978 * Permission is hereby granted, free of charge, to any person obtaining
3979 * a copy of this software and associated documentation files (the
3980 * "Software"), to deal in the Software without restriction, including
3981 * without limitation the rights to use, copy, modify, merge, publish,
3982 * distribute, sublicense, and/or sell copies of the Software, and to
3983 * permit persons to whom the Software is furnished to do so, subject to
3984 * the following conditions:
3985 *
3986 * The above copyright notice and this permission notice shall be included
3987 * in all copies or substantial portions of the Software.
3988 *
3989 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3990 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3991 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3992 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3993 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3994 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3995 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3996 */
3997
3998 #include "precompiled.h"
3999
4000 #include "lib/sysdep/sysdep.h"
4001
4002-#define GNU_SOURCE
4003-#include "mocks/dlfcn.h"
4004-#include "mocks/unistd.h"
4005-
4006+#define _GNU_SOURCE
4007+#include <dlfcn.h>
4008 #include <cstdio>
4009+#include <unistd.h>
4010
4011 OsPath unix_ExecutablePathname()
4012 {
4013 // Find the executable's filename
4014 Dl_info dl_info;
4015 memset(&dl_info, 0, sizeof(dl_info));
4016- if (!T::dladdr((void *)sys_ExecutablePathname, &dl_info) || !dl_info.dli_fname)
4017+ if (!dladdr((void *)sys_ExecutablePathname, &dl_info) || !dl_info.dli_fname)
4018 return OsPath();
4019 const char* path = dl_info.dli_fname;
4020
4021 // If this looks like an absolute path, use realpath to get the normalized
4022 // path (with no '.' or '..')
4023 if (path[0] == '/')
4024 {
4025 char resolved[PATH_MAX];
4026 if (!realpath(path, resolved))
4027 return OsPath();
4028 return resolved;
4029 }
4030
4031 // If this looks like a relative path, resolve against cwd and use realpath
4032 if (strchr(path, '/'))
4033 {
4034 char cwd[PATH_MAX];
4035- if (!T::getcwd(cwd, PATH_MAX))
4036+ if (!getcwd(cwd, PATH_MAX))
4037 return OsPath();
4038
4039 char absolute[PATH_MAX];
4040 int ret = snprintf(absolute, PATH_MAX, "%s/%s", cwd, path);
4041 if (ret < 0 || ret >= PATH_MAX)
4042 return OsPath(); // path too long, or other error
4043 char resolved[PATH_MAX];
4044 if (!realpath(absolute, resolved))
4045 return OsPath();
4046 return resolved;
4047 }
4048
4049 // If it's not a path at all, i.e. it's just a filename, we'd
4050 // probably have to search through PATH to find it.
4051 // That's complex and should be uncommon, so don't bother.
4052 return OsPath();
4053 }
4054Index: source/lib/sysdep/os/unix/x/x.cpp
4055===================================================================
4056--- source/lib/sysdep/os/unix/x/x.cpp (revision 23275)
4057+++ source/lib/sysdep/os/unix/x/x.cpp (nonexistent)
4058@@ -1,491 +0,0 @@
4059-/* Copyright (C) 2019 Wildfire Games.
4060- *
4061- * Permission is hereby granted, free of charge, to any person obtaining
4062- * a copy of this software and associated documentation files (the
4063- * "Software"), to deal in the Software without restriction, including
4064- * without limitation the rights to use, copy, modify, merge, publish,
4065- * distribute, sublicense, and/or sell copies of the Software, and to
4066- * permit persons to whom the Software is furnished to do so, subject to
4067- * the following conditions:
4068- *
4069- * The above copyright notice and this permission notice shall be included
4070- * in all copies or substantial portions of the Software.
4071- *
4072- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4073- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4074- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4075- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4076- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4077- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4078- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4079- */
4080-
4081-// X Window System-specific code
4082-
4083-#include "precompiled.h"
4084-
4085-#if OS_LINUX || OS_BSD
4086-# define HAVE_X 1
4087-#else
4088-# define HAVE_X 0
4089-#endif
4090-
4091-#if HAVE_X
4092-
4093-#include "lib/debug.h"
4094-#include "lib/utf8.h"
4095-#include "lib/sysdep/gfx.h"
4096-#include "lib/sysdep/cursor.h"
4097-
4098-#include "ps/VideoMode.h"
4099-
4100-#define Cursor X__Cursor
4101-
4102-#include <X11/Xlib.h>
4103-#include <stdlib.h>
4104-#include <X11/Xatom.h>
4105-#include <X11/Xcursor/Xcursor.h>
4106-
4107-#include "SDL.h"
4108-#include "SDL_syswm.h"
4109-
4110-#include <algorithm>
4111-#undef Cursor
4112-#undef Status
4113-
4114-static Display *g_SDL_Display;
4115-static Window g_SDL_Window;
4116-static wchar_t *selection_data=NULL;
4117-static size_t selection_size=0;
4118-
4119-namespace gfx {
4120-
4121-Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
4122-{
4123- Display* disp = XOpenDisplay(0);
4124- if(!disp)
4125- WARN_RETURN(ERR::FAIL);
4126-
4127- int screen = XDefaultScreen(disp);
4128-
4129- /* 2004-07-13
4130- NOTE: The XDisplayWidth/Height functions don't actually return the current
4131- display mode - they return the size of the root window. This means that
4132- users with "Virtual Desktops" bigger than what their monitors/graphics
4133- card can handle will have to set their 0AD screen resolution manually.
4134-
4135- There's supposed to be an X extension that can give you the actual display
4136- mode, probably including refresh rate info etc, but it's not worth
4137- researching and implementing that at this stage.
4138- */
4139-
4140- if(xres)
4141- *xres = XDisplayWidth(disp, screen);
4142- if(yres)
4143- *yres = XDisplayHeight(disp, screen);
4144- if(bpp)
4145- *bpp = XDefaultDepth(disp, screen);
4146- if(freq)
4147- *freq = 0;
4148- XCloseDisplay(disp);
4149- return INFO::OK;
4150-}
4151-
4152-
4153-Status GetMonitorSize(int& width_mm, int& height_mm)
4154-{
4155- Display* disp = XOpenDisplay(0);
4156- if(!disp)
4157- WARN_RETURN(ERR::FAIL);
4158-
4159- int screen = XDefaultScreen(disp);
4160-
4161- width_mm = XDisplayWidthMM(disp, screen);
4162- height_mm = XDisplayHeightMM(disp, screen);
4163-
4164- XCloseDisplay(disp);
4165- return INFO::OK;
4166-}
4167-
4168-} // namespace gfx
4169-
4170-
4171-static bool get_wminfo(SDL_SysWMinfo& wminfo)
4172-{
4173- SDL_VERSION(&wminfo.version);
4174-
4175- const int ret = SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &wminfo);
4176-
4177- if(ret == 1)
4178- return true;
4179-
4180- if(ret == -1)
4181- {
4182- debug_printf("SDL_GetWMInfo failed\n");
4183- return false;
4184- }
4185- if(ret == 0)
4186- {
4187- debug_printf("SDL_GetWMInfo is not implemented on this platform\n");
4188- return false;
4189- }
4190-
4191- debug_printf("SDL_GetWMInfo returned an unknown value: %d\n", ret);
4192- return false;
4193-}
4194-
4195-/*
4196-Oh, boy, this is heavy stuff...
4197-
4198-<User-End Stuff - Definitions and Conventions>
4199-http://www.freedesktop.org/standards/clipboards-spec/clipboards.txt
4200-<Technical, API stuff>
4201-http://www.mail-archive.com/xfree86@xfree86.org/msg15594.html
4202-http://michael.toren.net/mirrors/doc/X-copy+paste.txt
4203-http://devdocs.wesnoth.org/clipboard_8cpp-source.html
4204-http://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html
4205-http://www.jwz.org/doc/x-cut-and-paste.html
4206-
4207-The basic run-down on X Selection Handling:
4208-* One window owns the "current selection" at any one time
4209-* Accessing the Selection (i.e. "paste"), Step-by-step
4210- * Ask the X server for the current selection owner
4211- * Ask the selection owner window to convert the selection into a format
4212- we can understand (XA_STRING - Latin-1 string - for now)
4213- * The converted result is stored as a property of the *selection owner*
4214- window. It is possible to specify the current application window as the
4215- target - but that'd require some X message handling... easier to skip that..
4216- * The final step is to acquire the property value of the selection owner
4217- window
4218-
4219-Notes:
4220-An "Atom" is a server-side object that represents a string by an index into some
4221-kind of table or something. Pretty much like a handle that refers to one unique
4222-string. Atoms are used here to refer to property names and value formats.
4223-
4224-Expansions:
4225-* Implement UTF-8 format support (should be interresting for international users)
4226-
4227-*/
4228-wchar_t *sys_clipboard_get()
4229-{
4230- Display *disp=XOpenDisplay(NULL);
4231- if(!disp)
4232- return NULL;
4233-
4234- // We use CLIPBOARD as the default, since the CLIPBOARD semantics are much
4235- // closer to windows semantics.
4236- Atom selSource=XInternAtom(disp, "CLIPBOARD", False);
4237-
4238- Window selOwner=XGetSelectionOwner(disp, selSource);
4239- if(selOwner == None)
4240- {
4241- // However, since many apps don't use CLIPBOARD, but use PRIMARY instead
4242- // we use XA_PRIMARY as a fallback clipboard. This is true for xterm,
4243- // for example.
4244- selSource=XA_PRIMARY;
4245- selOwner=XGetSelectionOwner(disp, selSource);
4246- }
4247- if(selOwner != None) {
4248- Atom pty=XInternAtom(disp, "SelectionPropertyTemp", False);
4249- XConvertSelection(disp, selSource, XA_STRING, pty, selOwner, CurrentTime);
4250- XFlush(disp);
4251-
4252- Atom type;
4253- int format=0, result=0;
4254- unsigned long len=0, bytes_left=0, dummy=0;
4255- u8 *data=NULL;
4256-
4257- // Get the length of the property and some attributes
4258- // bytes_left will contain the length of the selection
4259- result = XGetWindowProperty (disp, selOwner, pty,
4260- 0, 0, // offset - len
4261- 0, // Delete 0==FALSE
4262- AnyPropertyType,//flag
4263- &type, // return type
4264- &format, // return format
4265- &len, &bytes_left,
4266- &data);
4267- if(result != Success)
4268- debug_printf("clipboard_get: XGetWindowProperty failed! result: %d type:%lu len:%lu format:%d bytes_left:%lu\n",
4269- result, type, len, format, bytes_left);
4270- if(result == Success && bytes_left > 0)
4271- {
4272- result = XGetWindowProperty (disp, selOwner,
4273- pty, 0, bytes_left, 0,
4274- AnyPropertyType, &type, &format,
4275- &len, &dummy, &data);
4276-
4277- if(result == Success)
4278- {
4279- if(type == XA_STRING) //Latin-1: Just copy into low byte of wchar_t
4280- {
4281- wchar_t *ret=(wchar_t *)malloc((bytes_left+1)*sizeof(wchar_t));
4282- std::copy(data, data+bytes_left, ret);
4283- ret[bytes_left]=0;
4284- return ret;
4285- }
4286- // TODO: Handle UTF8 strings
4287- }
4288- else
4289- {
4290- debug_printf("clipboard_get: XGetWindowProperty failed!\n");
4291- return NULL;
4292- }
4293- }
4294- }
4295-
4296- return NULL;
4297-}
4298-
4299-Status sys_clipboard_free(wchar_t *clip_buf)
4300-{
4301- free(clip_buf);
4302- return INFO::OK;
4303-}
4304-
4305-/**
4306- * An SDL Event filter that intercepts other applications' requests for the
4307- * X selection buffer.
4308- *
4309- * @see x11_clipboard_init
4310- * @see sys_clipboard_set
4311- */
4312-int clipboard_filter(void* UNUSED(userdata), SDL_Event* event)
4313-{
4314- /* Pass on all non-window manager specific events immediately */
4315- /* And do nothing if we don't actually have a clip-out to send out */
4316- if(event->type != SDL_SYSWMEVENT || !selection_data)
4317- return 1;
4318-
4319- /* Handle window-manager specific clipboard events */
4320- /* (Note: libsdl must be compiled with X11 support (SDL_VIDEO_DRIVER_X11 in SDL_config.h) -
4321- else you'll get errors like "'struct SDL_SysWMmsg' has no member named 'xevent'") */
4322- XEvent* xevent = &event->syswm.msg->msg.x11.event;
4323- switch(xevent->type) {
4324- /* Copy the selection from our buffer to the requested property, and
4325- convert to the requested target format */
4326- case SelectionRequest: {
4327- XSelectionRequestEvent *req;
4328- XEvent sevent;
4329-
4330- req = &xevent->xselectionrequest;
4331- sevent.xselection.type = SelectionNotify;
4332- sevent.xselection.display = req->display;
4333- sevent.xselection.selection = req->selection;
4334- sevent.xselection.target = req->target;
4335- sevent.xselection.property = None;
4336- sevent.xselection.requestor = req->requestor;
4337- sevent.xselection.time = req->time;
4338- // Simply strip all non-Latin1 characters and replace with '?'
4339- // We should support XA_UTF8
4340- if(req->target == XA_STRING)
4341- {
4342- size_t size = wcslen(selection_data);
4343- u8* buf = (u8*)alloca(size);
4344-
4345- for(size_t i = 0; i < size; i++)
4346- {
4347- buf[i] = selection_data[i] < 0x100 ? selection_data[i] : '?';
4348- }
4349-
4350- XChangeProperty(g_SDL_Display, req->requestor, req->property,
4351- sevent.xselection.target, 8, PropModeReplace,
4352- buf, size);
4353- sevent.xselection.property = req->property;
4354- }
4355- // TODO Add more target formats
4356- XSendEvent(g_SDL_Display, req->requestor, False, 0, &sevent);
4357- XSync(g_SDL_Display, False);
4358- }
4359- break;
4360- }
4361-
4362- return 1;
4363-}
4364-
4365-/**
4366- * Initialization for X clipboard handling, called on-demand by
4367- * sys_clipboard_set.
4368- */
4369-Status x11_clipboard_init()
4370-{
4371- SDL_SysWMinfo info;
4372-
4373- if(get_wminfo(info))
4374- {
4375- /* Save the information for later use */
4376- if(info.subsystem == SDL_SYSWM_X11)
4377- {
4378- g_SDL_Display = info.info.x11.display;
4379- g_SDL_Window = info.info.x11.window;
4380-
4381- /* Enable the special window hook events */
4382- SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
4383- SDL_SetEventFilter(clipboard_filter, NULL);
4384-
4385- return INFO::OK;
4386- }
4387- else
4388- {
4389- return ERR::FAIL;
4390- }
4391- }
4392-
4393- return INFO::OK;
4394-}
4395-
4396-/**
4397- * Set the Selection (i.e. "copy")
4398- *
4399- * Step-by-step (X11)
4400- * <ul>
4401- * <li>Store the selection text in a local buffer
4402- * <li>Tell the X server that we want to own the selection
4403- * <li>Listen for Selection events and respond to them as appropriate
4404- * </ul>
4405- */
4406-Status sys_clipboard_set(const wchar_t *str)
4407-{
4408- ONCE(x11_clipboard_init());
4409-
4410- if(selection_data)
4411- {
4412- free(selection_data);
4413- selection_data = NULL;
4414- }
4415-
4416- selection_size = (wcslen(str)+1)*sizeof(wchar_t);
4417- selection_data = (wchar_t *)malloc(selection_size);
4418- wcscpy(selection_data, str);
4419-
4420- // Like for the clipboard_get code above, we rather use CLIPBOARD than
4421- // PRIMARY - more windows'y behaviour there.
4422- Atom clipboard_atom = XInternAtom(g_SDL_Display, "CLIPBOARD", False);
4423- XSetSelectionOwner(g_SDL_Display, clipboard_atom, g_SDL_Window, CurrentTime);
4424- XSetSelectionOwner(g_SDL_Display, XA_PRIMARY, g_SDL_Window, CurrentTime);
4425-
4426- // SDL2 doesn't have a lockable event thread, so it just uses
4427- // XSync directly instead of lock_func/unlock_func
4428- XSync(g_SDL_Display, False);
4429-
4430- return INFO::OK;
4431-}
4432-
4433-struct sys_cursor_impl
4434-{
4435- XcursorImage* image;
4436- X__Cursor cursor;
4437-};
4438-
4439-static XcursorPixel cursor_pixel_to_x11_format(const XcursorPixel& bgra_pixel)
4440-{
4441- BOOST_STATIC_ASSERT(sizeof(XcursorPixel) == 4 * sizeof(u8));
4442- XcursorPixel ret;
4443- u8* dst = reinterpret_cast<u8*>(&ret);
4444- const u8* b = reinterpret_cast<const u8*>(&bgra_pixel);
4445- const u8 a = b[3];
4446-
4447- for(size_t i = 0; i < 3; ++i)
4448- *dst++ = (b[i]) * a / 255;
4449- *dst = a;
4450- return ret;
4451-}
4452-
4453-Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
4454-{
4455- debug_printf("sys_cursor_create: using Xcursor to create %d x %d cursor\n", w, h);
4456- XcursorImage* image = XcursorImageCreate(w, h);
4457- if(!image)
4458- WARN_RETURN(ERR::FAIL);
4459-
4460- const XcursorPixel* bgra_img_begin = reinterpret_cast<XcursorPixel*>(bgra_img);
4461- std::transform(bgra_img_begin, bgra_img_begin + (w*h), image->pixels,
4462- cursor_pixel_to_x11_format);
4463- image->xhot = hx;
4464- image->yhot = hy;
4465-
4466- SDL_SysWMinfo wminfo;
4467- if(!get_wminfo(wminfo))
4468- WARN_RETURN(ERR::FAIL);
4469-
4470- sys_cursor_impl* impl = new sys_cursor_impl;
4471- impl->image = image;
4472- impl->cursor = XcursorImageLoadCursor(wminfo.info.x11.display, image);
4473- if(impl->cursor == None)
4474- WARN_RETURN(ERR::FAIL);
4475-
4476- *cursor = static_cast<sys_cursor>(impl);
4477- return INFO::OK;
4478-}
4479-
4480-// returns a dummy value representing an empty cursor
4481-Status sys_cursor_create_empty(sys_cursor* cursor)
4482-{
4483- static u8 transparent_bgra[] = { 0x0, 0x0, 0x0, 0x0 };
4484-
4485- return sys_cursor_create(1, 1, static_cast<void*>(transparent_bgra), 0, 0, cursor);
4486-}
4487-
4488-// replaces the current system cursor with the one indicated. need only be
4489-// called once per cursor; pass 0 to restore the default.
4490-Status sys_cursor_set(sys_cursor cursor)
4491-{
4492- if(!cursor) // restore default cursor
4493- SDL_ShowCursor(SDL_DISABLE);
4494- else
4495- {
4496- SDL_SysWMinfo wminfo;
4497- if(!get_wminfo(wminfo))
4498- WARN_RETURN(ERR::FAIL);
4499-
4500- if(wminfo.subsystem != SDL_SYSWM_X11)
4501- WARN_RETURN(ERR::FAIL);
4502-
4503- SDL_ShowCursor(SDL_ENABLE);
4504-
4505- Window window;
4506- if(wminfo.info.x11.window)
4507- window = wminfo.info.x11.window;
4508- else
4509- WARN_RETURN(ERR::FAIL);
4510-
4511- XDefineCursor(wminfo.info.x11.display, window,
4512- static_cast<sys_cursor_impl*>(cursor)->cursor);
4513- // SDL2 doesn't have a lockable event thread, so it just uses
4514- // XSync directly instead of lock_func/unlock_func
4515- XSync(wminfo.info.x11.display, False);
4516- }
4517-
4518- return INFO::OK;}
4519-
4520-// destroys the indicated cursor and frees its resources. if it is
4521-// currently the system cursor, the default cursor is restored first.
4522-Status sys_cursor_free(sys_cursor cursor)
4523-{
4524- // bail now to prevent potential confusion below; there's nothing to do.
4525- if(!cursor)
4526- return INFO::OK;
4527-
4528- sys_cursor_set(0); // restore default cursor
4529- sys_cursor_impl* impl = static_cast<sys_cursor_impl*>(cursor);
4530-
4531- XcursorImageDestroy(impl->image);
4532-
4533- SDL_SysWMinfo wminfo;
4534- if(!get_wminfo(wminfo))
4535- return ERR::FAIL;
4536- XFreeCursor(wminfo.info.x11.display, impl->cursor);
4537-
4538- delete impl;
4539-
4540- return INFO::OK;
4541-}
4542-
4543-Status sys_cursor_reset()
4544-{
4545- return INFO::OK;
4546-}
4547-
4548-
4549-#endif // #if HAVE_X
4550
4551Property changes on: source/lib/sysdep/os/unix/x/x.cpp
4552___________________________________________________________________
4553Deleted: svn:eol-style
4554## -1 +0,0 ##
4555-native
4556\ No newline at end of property
4557Index: source/lib/sysdep/os/win/wclipboard.cpp
4558===================================================================
4559--- source/lib/sysdep/os/win/wclipboard.cpp (revision 23275)
4560+++ source/lib/sysdep/os/win/wclipboard.cpp (nonexistent)
4561@@ -1,123 +0,0 @@
4562-/* Copyright (C) 2010 Wildfire Games.
4563- *
4564- * Permission is hereby granted, free of charge, to any person obtaining
4565- * a copy of this software and associated documentation files (the
4566- * "Software"), to deal in the Software without restriction, including
4567- * without limitation the rights to use, copy, modify, merge, publish,
4568- * distribute, sublicense, and/or sell copies of the Software, and to
4569- * permit persons to whom the Software is furnished to do so, subject to
4570- * the following conditions:
4571- *
4572- * The above copyright notice and this permission notice shall be included
4573- * in all copies or substantial portions of the Software.
4574- *
4575- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4576- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4577- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4578- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4579- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4580- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4581- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4582- */
4583-
4584-#include "precompiled.h"
4585-#include "lib/sysdep/clipboard.h"
4586-
4587-#include "lib/sysdep/os/win/win.h"
4588-#include "lib/sysdep/os/win/wutil.h"
4589-
4590-
4591-// caller is responsible for freeing hMem.
4592-static Status SetClipboardText(const wchar_t* text, HGLOBAL& hMem)
4593-{
4594- const size_t numChars = wcslen(text);
4595- hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, (numChars + 1) * sizeof(wchar_t));
4596- if(!hMem)
4597- WARN_RETURN(ERR::NO_MEM);
4598-
4599- wchar_t* lockedText = (wchar_t*)GlobalLock(hMem);
4600- if(!lockedText)
4601- WARN_RETURN(ERR::NO_MEM);
4602- wcscpy_s(lockedText, numChars+1, text);
4603- GlobalUnlock(hMem);
4604-
4605- HANDLE hData = SetClipboardData(CF_UNICODETEXT, hMem);
4606- if(!hData) // failed
4607- WARN_RETURN(ERR::FAIL);
4608-
4609- return INFO::OK;
4610-}
4611-
4612-
4613-// @return INFO::OK iff text has been assigned a pointer (which the
4614-// caller must free via sys_clipboard_free) to the clipboard text.
4615-static Status GetClipboardText(wchar_t*& text)
4616-{
4617- // NB: Windows NT/2000+ auto convert CF_UNICODETEXT <-> CF_TEXT.
4618-
4619- if(!IsClipboardFormatAvailable(CF_UNICODETEXT))
4620- return INFO::CANNOT_HANDLE;
4621-
4622- HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT);
4623- if(!hMem)
4624- WARN_RETURN(ERR::FAIL);
4625-
4626- const wchar_t* lockedText = (const wchar_t*)GlobalLock(hMem);
4627- if(!lockedText)
4628- WARN_RETURN(ERR::NO_MEM);
4629-
4630- const size_t size = GlobalSize(hMem);
4631- text = (wchar_t*)malloc(size);
4632- if(!text)
4633- WARN_RETURN(ERR::NO_MEM);
4634- wcscpy_s(text, size/sizeof(wchar_t), lockedText);
4635-
4636- (void)GlobalUnlock(hMem);
4637-
4638- return INFO::OK;
4639-}
4640-
4641-
4642-// OpenClipboard parameter.
4643-// NB: using wutil_AppWindow() causes GlobalLock to fail.
4644-static const HWND hWndNewOwner = 0; // MSDN: associate with "current task"
4645-
4646-Status sys_clipboard_set(const wchar_t* text)
4647-{
4648- if(!OpenClipboard(hWndNewOwner))
4649- WARN_RETURN(ERR::FAIL);
4650-
4651- WARN_IF_FALSE(EmptyClipboard());
4652-
4653- // NB: to enable copy/pasting something other than text, add
4654- // message handlers for WM_RENDERFORMAT and WM_RENDERALLFORMATS.
4655- HGLOBAL hMem;
4656- Status ret = SetClipboardText(text, hMem);
4657-
4658- WARN_IF_FALSE(CloseClipboard()); // must happen before GlobalFree
4659-
4660- ENSURE(GlobalFree(hMem) == 0); // (0 indicates success)
4661-
4662- return ret;
4663-}
4664-
4665-
4666-wchar_t* sys_clipboard_get()
4667-{
4668- if(!OpenClipboard(hWndNewOwner))
4669- return 0;
4670-
4671- wchar_t* text;
4672- Status ret = GetClipboardText(text);
4673-
4674- WARN_IF_FALSE(CloseClipboard());
4675-
4676- return (ret == INFO::OK)? text : 0;
4677-}
4678-
4679-
4680-Status sys_clipboard_free(wchar_t* text)
4681-{
4682- free(text);
4683- return INFO::OK;
4684-}
4685
4686Property changes on: source/lib/sysdep/os/win/wclipboard.cpp
4687___________________________________________________________________
4688Deleted: svn:eol-style
4689## -1 +0,0 ##
4690-native
4691\ No newline at end of property
4692Index: source/lib/sysdep/os/win/wcursor.cpp
4693===================================================================
4694--- source/lib/sysdep/os/win/wcursor.cpp (revision 23275)
4695+++ source/lib/sysdep/os/win/wcursor.cpp (nonexistent)
4696@@ -1,142 +0,0 @@
4697-/* Copyright (C) 2018 Wildfire Games.
4698- *
4699- * Permission is hereby granted, free of charge, to any person obtaining
4700- * a copy of this software and associated documentation files (the
4701- * "Software"), to deal in the Software without restriction, including
4702- * without limitation the rights to use, copy, modify, merge, publish,
4703- * distribute, sublicense, and/or sell copies of the Software, and to
4704- * permit persons to whom the Software is furnished to do so, subject to
4705- * the following conditions:
4706- *
4707- * The above copyright notice and this permission notice shall be included
4708- * in all copies or substantial portions of the Software.
4709- *
4710- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4711- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4712- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4713- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4714- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4715- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4716- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4717- */
4718-
4719-#include "precompiled.h"
4720-#include "lib/sysdep/cursor.h"
4721-
4722-#include "lib/sysdep/gfx.h"
4723-#include "lib/sysdep/os/win/win.h"
4724-#include "lib/sysdep/os/win/wutil.h"
4725-
4726-static sys_cursor cursor_from_HICON(HICON hIcon)
4727-{
4728- return (sys_cursor)(uintptr_t)hIcon;
4729-}
4730-
4731-static sys_cursor cursor_from_HCURSOR(HCURSOR hCursor)
4732-{
4733- return (sys_cursor)(uintptr_t)hCursor;
4734-}
4735-
4736-static HICON HICON_from_cursor(sys_cursor cursor)
4737-{
4738- return (HICON)(uintptr_t)cursor;
4739-}
4740-
4741-static HCURSOR HCURSOR_from_cursor(sys_cursor cursor)
4742-{
4743- return (HCURSOR)(uintptr_t)cursor;
4744-}
4745-
4746-
4747-static Status sys_cursor_create_common(int w, int h, void* bgra_img, void* mask_img, int hx, int hy, sys_cursor* cursor)
4748-{
4749- *cursor = 0;
4750-
4751- // MSDN says selecting this HBITMAP into a DC is slower since we use
4752- // CreateBitmap; bpp/format must be checked against those of the DC.
4753- // this is the simplest way and we don't care about slight performance
4754- // differences because this is typically only called once.
4755- HBITMAP hbmColor = CreateBitmap(w, h, 1, 32, bgra_img);
4756-
4757- // CreateIconIndirect doesn't access this; we just need to pass
4758- // an empty bitmap.
4759- HBITMAP hbmMask = CreateBitmap(w, h, 1, 1, mask_img);
4760-
4761- // create the cursor (really an icon; they differ only in
4762- // fIcon and the hotspot definitions).
4763- ICONINFO ii;
4764- ii.fIcon = FALSE; // cursor
4765- ii.xHotspot = (DWORD)hx;
4766- ii.yHotspot = (DWORD)hy;
4767- ii.hbmMask = hbmMask;
4768- ii.hbmColor = hbmColor;
4769- HICON hIcon = CreateIconIndirect(&ii);
4770-
4771- // CreateIconIndirect makes copies, so we no longer need these.
4772- DeleteObject(hbmMask);
4773- DeleteObject(hbmColor);
4774-
4775- if(!wutil_IsValidHandle(hIcon))
4776- WARN_RETURN(ERR::FAIL);
4777-
4778- *cursor = cursor_from_HICON(hIcon);
4779- return INFO::OK;
4780-}
4781-
4782-Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor)
4783-{
4784- // alpha-blended cursors do not work on a 16-bit display
4785- // (they get drawn as a black square), so refuse to load the
4786- // cursor in that case
4787- int bpp = 0;
4788- RETURN_STATUS_IF_ERR(gfx::GetVideoMode(NULL, NULL, &bpp, NULL));
4789- if (bpp <= 16)
4790- return ERR::FAIL;
4791-
4792- return sys_cursor_create_common(w, h, bgra_img, NULL, hx, hy, cursor);
4793-}
4794-
4795-Status sys_cursor_create_empty(sys_cursor* cursor)
4796-{
4797- // the mask gets ignored on 32-bit displays, but is used on 16-bit displays;
4798- // setting it to 0xFF makes the cursor invisible (though I'm not quite
4799- // sure why it's that way round)
4800- u8 bgra_img[] = {0, 0, 0, 0};
4801- u8 mask_img[] = {0xFF};
4802- return sys_cursor_create_common(1, 1, bgra_img, mask_img, 0, 0, cursor);
4803-}
4804-
4805-
4806-Status sys_cursor_set(sys_cursor cursor)
4807-{
4808- // restore default cursor.
4809- if(!cursor)
4810- cursor = cursor_from_HCURSOR(LoadCursor(0, IDC_ARROW));
4811-
4812- (void)SetCursor(HCURSOR_from_cursor(cursor));
4813- // return value (previous cursor) is useless.
4814-
4815- return INFO::OK;
4816-}
4817-
4818-
4819-Status sys_cursor_free(sys_cursor cursor)
4820-{
4821- // bail now to prevent potential confusion below; there's nothing to do.
4822- if(!cursor)
4823- return INFO::OK;
4824-
4825- // if the cursor being freed is active, restore the default arrow
4826- // (just for safety).
4827- if(cursor_from_HCURSOR(GetCursor()) == cursor)
4828- WARN_IF_ERR(sys_cursor_set(0));
4829-
4830- if(!DestroyIcon(HICON_from_cursor(cursor)))
4831- WARN_RETURN(StatusFromWin());
4832- return INFO::OK;
4833-}
4834-
4835-Status sys_cursor_reset()
4836-{
4837- return INFO::OK;
4838-}
4839
4840Property changes on: source/lib/sysdep/os/win/wcursor.cpp
4841___________________________________________________________________
4842Deleted: svn:eol-style
4843## -1 +0,0 ##
4844-native
4845\ No newline at end of property
4846Index: source/lib/sysdep/os/win/wgfx.cpp
4847===================================================================
4848--- source/lib/sysdep/os/win/wgfx.cpp (revision 23275)
4849+++ source/lib/sysdep/os/win/wgfx.cpp (working copy)
4850@@ -1,187 +1,145 @@
4851 /* Copyright (C) 2017 Wildfire Games.
4852 *
4853 * Permission is hereby granted, free of charge, to any person obtaining
4854 * a copy of this software and associated documentation files (the
4855 * "Software"), to deal in the Software without restriction, including
4856 * without limitation the rights to use, copy, modify, merge, publish,
4857 * distribute, sublicense, and/or sell copies of the Software, and to
4858 * permit persons to whom the Software is furnished to do so, subject to
4859 * the following conditions:
4860 *
4861 * The above copyright notice and this permission notice shall be included
4862 * in all copies or substantial portions of the Software.
4863 *
4864 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4865 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4866 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4867 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4868 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4869 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4870 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4871 */
4872
4873 /*
4874 * graphics card detection on Windows.
4875 */
4876
4877 #include "precompiled.h"
4878 #include "lib/sysdep/os/win/wgfx.h"
4879
4880 #include "lib/sysdep/gfx.h"
4881 #include "lib/sysdep/os/win/wdll_ver.h"
4882 #include "lib/sysdep/os/win/wutil.h"
4883
4884 #if MSC_VERSION
4885 #pragma comment(lib, "advapi32.lib") // registry
4886 #endif
4887
4888
4889 // note: this implementation doesn't require OpenGL to be initialized.
4890 static Status AppendDriverVersionsFromRegistry(VersionList& versionList)
4891 {
4892 // rationale:
4893 // - we could easily determine the 2d driver via EnumDisplaySettings,
4894 // but we want to query the actual OpenGL driver. see
4895 // http://www.opengl.org/discussion_boards/ubb/Forum3/HTML/009679.html ;
4896 // in short, we need the exact OpenGL driver version because some
4897 // driver packs (e.g. Omega) mix and match DLLs.
4898 // - an alternative implementation would be to enumerate all
4899 // DLLs loaded into the process, and check for a glBegin export.
4900 // that requires toolhelp/PSAPI (a bit complicated) and telling
4901 // ICD/opengl32.dll apart (not future-proof).
4902 // - therefore, we stick with the OpenGLDrivers approach. since there is
4903 // no good way to determine which of the subkeys (e.g. nvoglnt) is
4904 // active (several may exist due to previously removed drivers),
4905 // we just display all of them. it is obvious from looking at
4906 // gfx_card which one is correct; we thus avoid driver-specific
4907 // name checks and reporting incorrectly.
4908
4909 wchar_t dllName[MAX_PATH+1];
4910
4911 HKEY hkDrivers;
4912 const wchar_t* key = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\OpenGLDrivers";
4913 // (we've received word of this failing on one WinXP system, but
4914 // AppendDriverVersionsFromKnownFiles might still work.)
4915 if(RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hkDrivers) != 0)
4916 return ERR::FAIL; // NOWARN (see above)
4917
4918 // for each subkey (i.e. installed OpenGL driver):
4919 for(DWORD i = 0; ; i++)
4920 {
4921 wchar_t driverName[32];
4922 DWORD driverNameLength = ARRAY_SIZE(driverName);
4923 const LONG err = RegEnumKeyExW(hkDrivers, i, driverName, &driverNameLength, 0, 0, 0, 0);
4924 if(err == ERROR_NO_MORE_ITEMS)
4925 {
4926 if(i == 0)
4927 {
4928 RegCloseKey(hkDrivers);
4929 return ERR::NOT_SUPPORTED; // NOWARN (ATI and NVidia don't create sub-keys on Windows 7)
4930 }
4931 break;
4932 }
4933 ENSURE(err == ERROR_SUCCESS);
4934
4935 HKEY hkDriver;
4936 if(RegOpenKeyExW(hkDrivers, driverName, 0, KEY_QUERY_VALUE, &hkDriver) == 0)
4937 {
4938 DWORD dllNameLength = ARRAY_SIZE(dllName)-5; // for ".dll"
4939 if(RegQueryValueExW(hkDriver, L"Dll", 0, 0, (LPBYTE)dllName, &dllNameLength) == 0)
4940 wdll_ver_Append(dllName, versionList);
4941
4942 RegCloseKey(hkDriver);
4943 }
4944 }
4945
4946 // for each value:
4947 // (some old drivers, e.g. S3 Super Savage, store their ICD name in a
4948 // single REG_SZ value. we therefore include those as well.)
4949 for(DWORD i = 0; ; i++)
4950 {
4951 wchar_t name[100]; // we don't need this, but RegEnumValue fails otherwise.
4952 DWORD nameLength = ARRAY_SIZE(name);
4953 DWORD type;
4954 DWORD dllNameLength = ARRAY_SIZE(dllName)-5; // for ".dll"
4955 const DWORD err = RegEnumValueW(hkDrivers, i, name, &nameLength, 0, &type, (LPBYTE)dllName, &dllNameLength);
4956 if(err == ERROR_NO_MORE_ITEMS)
4957 break;
4958 ENSURE(err == ERROR_SUCCESS);
4959 if(type == REG_SZ)
4960 wdll_ver_Append(dllName, versionList);
4961 }
4962
4963 RegCloseKey(hkDrivers);
4964
4965 return INFO::OK;
4966 }
4967
4968
4969 static void AppendDriverVersionsFromKnownFiles(VersionList& versionList)
4970 {
4971 // (check all known file names regardless of gfx_card, which may change and
4972 // defeat our parsing. this takes about 5..10 ms)
4973
4974 // NVidia
4975 wdll_ver_Append(L"nvoglv64.dll", versionList);
4976 wdll_ver_Append(L"nvoglv32.dll", versionList);
4977 wdll_ver_Append(L"nvoglnt.dll", versionList);
4978
4979 // ATI
4980 wdll_ver_Append(L"atioglxx.dll", versionList);
4981
4982 // Intel
4983 wdll_ver_Append(L"ig4icd32.dll", versionList);
4984 wdll_ver_Append(L"ig4icd64.dll", versionList);
4985 wdll_ver_Append(L"iglicd32.dll", versionList);
4986 }
4987
4988
4989 std::wstring wgfx_DriverInfo()
4990 {
4991 VersionList versionList;
4992 if(AppendDriverVersionsFromRegistry(versionList) != INFO::OK) // (fails on Windows 7)
4993 AppendDriverVersionsFromKnownFiles(versionList);
4994 return versionList;
4995 }
4996-
4997-
4998-//-----------------------------------------------------------------------------
4999-// direct implementations of some gfx functions
5000-
5001-namespace gfx {
5002-
5003-Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq)
5004-{
5005- DEVMODE dm = { sizeof(dm) };
5006-
5007- if(!EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm))
5008- WARN_RETURN(ERR::FAIL);
5009-
5010- // EnumDisplaySettings is documented to set the values of the following:
5011- const DWORD expectedFlags = DM_PELSWIDTH|DM_PELSHEIGHT|DM_BITSPERPEL|DM_DISPLAYFREQUENCY|DM_DISPLAYFLAGS;
5012- ENSURE((dm.dmFields & expectedFlags) == expectedFlags);
5013-
5014- if(xres)
5015- *xres = (int)dm.dmPelsWidth;
5016- if(yres)
5017- *yres = (int)dm.dmPelsHeight;
5018- if(bpp)
5019- *bpp = (int)dm.dmBitsPerPel;
5020- if(freq)
5021- *freq = (int)dm.dmDisplayFrequency;
5022-
5023- return INFO::OK;
5024-}
5025-
5026-
5027-Status GetMonitorSize(int& width_mm, int& height_mm)
5028-{
5029- // (DC for the primary monitor's entire screen)
5030- const HDC hDC = GetDC(0);
5031- width_mm = GetDeviceCaps(hDC, HORZSIZE);
5032- height_mm = GetDeviceCaps(hDC, VERTSIZE);
5033- ReleaseDC(0, hDC);
5034- return INFO::OK;
5035-}
5036-
5037-} // namespace gfx
5038Index: source/lib/sysdep/os/win/wsysdep.cpp
5039===================================================================
5040--- source/lib/sysdep/os/win/wsysdep.cpp (revision 23275)
5041+++ source/lib/sysdep/os/win/wsysdep.cpp (working copy)
5042@@ -1,612 +1,603 @@
5043 /* Copyright (C) 2011 Wildfire Games.
5044 *
5045 * Permission is hereby granted, free of charge, to any person obtaining
5046 * a copy of this software and associated documentation files (the
5047 * "Software"), to deal in the Software without restriction, including
5048 * without limitation the rights to use, copy, modify, merge, publish,
5049 * distribute, sublicense, and/or sell copies of the Software, and to
5050 * permit persons to whom the Software is furnished to do so, subject to
5051 * the following conditions:
5052 *
5053 * The above copyright notice and this permission notice shall be included
5054 * in all copies or substantial portions of the Software.
5055 *
5056 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
5057 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
5058 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
5059 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
5060 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
5061 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
5062 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5063 */
5064
5065 /*
5066 * Windows backend of the sysdep interface
5067 */
5068
5069 #include "precompiled.h"
5070 #include "lib/sysdep/sysdep.h"
5071
5072 #include "lib/alignment.h"
5073 #include "lib/sysdep/os/win/win.h" // includes windows.h; must come before shlobj
5074 #include <shlobj.h> // pick_dir
5075 #include <shellapi.h> // open_url
5076 #include <Wincrypt.h>
5077 #include <WindowsX.h> // message crackers
5078 #include <winhttp.h>
5079
5080-#include "lib/sysdep/clipboard.h"
5081 #include "lib/sysdep/os/win/error_dialog.h"
5082 #include "lib/sysdep/os/win/wutil.h"
5083
5084 #if CONFIG_ENABLE_BOOST
5085 # include <boost/algorithm/string.hpp>
5086 #endif
5087
5088
5089 #if MSC_VERSION
5090 #pragma comment(lib, "shell32.lib") // for sys_pick_directory SH* calls
5091 #pragma comment(lib, "winhttp.lib")
5092 #endif
5093
5094
5095 bool sys_IsDebuggerPresent()
5096 {
5097 return (IsDebuggerPresent() != 0);
5098 }
5099
5100
5101 std::wstring sys_WideFromArgv(const char* argv_i)
5102 {
5103 // NB: despite http://cbloomrants.blogspot.com/2008/06/06-14-08-1.html,
5104 // WinXP x64 EN cmd.exe (chcp reports 437) encodes argv u-umlaut
5105 // (entered manually or via auto-complete) via cp1252. the same applies
5106 // to WinXP SP2 DE (where chcp reports 850).
5107 const UINT cp = CP_ACP;
5108 const DWORD flags = MB_PRECOMPOSED|MB_ERR_INVALID_CHARS;
5109 const int inputSize = -1; // null-terminated
5110 std::vector<wchar_t> buf(strlen(argv_i)+1); // (upper bound on number of characters)
5111 // NB: avoid mbstowcs because it may specify another locale
5112 const int ret = MultiByteToWideChar(cp, flags, argv_i, (int)inputSize, &buf[0], (int)buf.size());
5113 ENSURE(ret != 0);
5114 return std::wstring(&buf[0]);
5115 }
5116
5117
5118 void sys_display_msg(const wchar_t* caption, const wchar_t* msg)
5119 {
5120 MessageBoxW(0, msg, caption, MB_ICONEXCLAMATION|MB_TASKMODAL|MB_SETFOREGROUND);
5121 }
5122
5123
5124 //-----------------------------------------------------------------------------
5125 // "program error" dialog (triggered by ENSURE and exception)
5126 //-----------------------------------------------------------------------------
5127
5128 // support for resizing the dialog / its controls (must be done manually)
5129
5130 static POINTS dlg_clientOrigin;
5131 static POINTS dlg_prevClientSize;
5132
5133 static void dlg_OnMove(HWND UNUSED(hDlg), int x, int y)
5134 {
5135 dlg_clientOrigin.x = (short)x;
5136 dlg_clientOrigin.y = (short)y;
5137 }
5138
5139
5140 static const size_t ANCHOR_LEFT = 0x01;
5141 static const size_t ANCHOR_RIGHT = 0x02;
5142 static const size_t ANCHOR_TOP = 0x04;
5143 static const size_t ANCHOR_BOTTOM = 0x08;
5144 static const size_t ANCHOR_ALL = 0x0F;
5145
5146 static void dlg_ResizeControl(HWND hDlg, int dlgItem, int dx, int dy, size_t anchors)
5147 {
5148 HWND hControl = GetDlgItem(hDlg, dlgItem);
5149 RECT r;
5150 GetWindowRect(hControl, &r);
5151
5152 int w = r.right - r.left, h = r.bottom - r.top;
5153 int x = r.left - dlg_clientOrigin.x, y = r.top - dlg_clientOrigin.y;
5154
5155 if(anchors & ANCHOR_RIGHT)
5156 {
5157 // right only
5158 if(!(anchors & ANCHOR_LEFT))
5159 x += dx;
5160 // horizontal (stretch width)
5161 else
5162 w += dx;
5163 }
5164
5165 if(anchors & ANCHOR_BOTTOM)
5166 {
5167 // bottom only
5168 if(!(anchors & ANCHOR_TOP))
5169 y += dy;
5170 // vertical (stretch height)
5171 else
5172 h += dy;
5173 }
5174
5175 SetWindowPos(hControl, 0, x,y, w,h, SWP_NOZORDER);
5176 }
5177
5178
5179 static void dlg_OnSize(HWND hDlg, UINT state, int clientSizeX, int clientSizeY)
5180 {
5181 // 'minimize' was clicked. we need to ignore this, otherwise
5182 // dx/dy would reduce some control positions to less than 0.
5183 // since Windows clips them, we wouldn't later be able to
5184 // reconstruct the previous values when 'restoring'.
5185 if(state == SIZE_MINIMIZED)
5186 return;
5187
5188 // NB: origin might legitimately be 0, but we know it is invalid
5189 // on the first call to this function, where dlg_prevClientSize is 0.
5190 const bool isOriginValid = (dlg_prevClientSize.y != 0);
5191
5192 const int dx = clientSizeX - dlg_prevClientSize.x;
5193 const int dy = clientSizeY - dlg_prevClientSize.y;
5194 dlg_prevClientSize.x = (short)clientSizeX;
5195 dlg_prevClientSize.y = (short)clientSizeY;
5196
5197 if(!isOriginValid) // must not call dlg_ResizeControl
5198 return;
5199
5200 dlg_ResizeControl(hDlg, IDC_CONTINUE, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
5201 dlg_ResizeControl(hDlg, IDC_SUPPRESS, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
5202 dlg_ResizeControl(hDlg, IDC_BREAK , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
5203 dlg_ResizeControl(hDlg, IDC_EXIT , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
5204 dlg_ResizeControl(hDlg, IDC_COPY , dx,dy, ANCHOR_RIGHT|ANCHOR_BOTTOM);
5205 dlg_ResizeControl(hDlg, IDC_EDIT1 , dx,dy, ANCHOR_ALL);
5206 }
5207
5208
5209 static void dlg_OnGetMinMaxInfo(HWND UNUSED(hDlg), LPMINMAXINFO mmi)
5210 {
5211 // we must make sure resize_control will never set negative coords -
5212 // Windows would clip them, and its real position would be lost.
5213 // restrict to a reasonable and good looking minimum size [pixels].
5214 mmi->ptMinTrackSize.x = 407;
5215 mmi->ptMinTrackSize.y = 159; // determined experimentally
5216 }
5217
5218
5219 struct DialogParams
5220 {
5221 const wchar_t* text;
5222 size_t flags;
5223 };
5224
5225 static BOOL dlg_OnInitDialog(HWND hDlg, HWND UNUSED(hWndFocus), LPARAM lParam)
5226 {
5227 const DialogParams* params = (const DialogParams*)lParam;
5228 HWND hWnd;
5229
5230 // need to reset for new instance of dialog
5231 dlg_clientOrigin.x = dlg_clientOrigin.y = 0;
5232 dlg_prevClientSize.x = dlg_prevClientSize.y = 0;
5233
5234 if(!(params->flags & DE_ALLOW_SUPPRESS))
5235 {
5236 hWnd = GetDlgItem(hDlg, IDC_SUPPRESS);
5237 EnableWindow(hWnd, FALSE);
5238 }
5239
5240 // set fixed font for readability
5241 hWnd = GetDlgItem(hDlg, IDC_EDIT1);
5242 HGDIOBJ hObj = (HGDIOBJ)GetStockObject(SYSTEM_FIXED_FONT);
5243 LPARAM redraw = FALSE;
5244 SendMessage(hWnd, WM_SETFONT, (WPARAM)hObj, redraw);
5245
5246 SetDlgItemTextW(hDlg, IDC_EDIT1, params->text);
5247 return TRUE; // set default keyboard focus
5248 }
5249
5250
5251 static void dlg_OnCommand(HWND hDlg, int id, HWND UNUSED(hWndCtl), UINT UNUSED(codeNotify))
5252 {
5253 switch(id)
5254 {
5255- case IDC_COPY:
5256- {
5257- std::vector<wchar_t> buf(128*KiB); // (too big for stack)
5258- GetDlgItemTextW(hDlg, IDC_EDIT1, &buf[0], (int)buf.size());
5259- sys_clipboard_set(&buf[0]);
5260- break;
5261- }
5262-
5263 case IDC_CONTINUE:
5264 EndDialog(hDlg, ERI_CONTINUE);
5265 break;
5266 case IDC_SUPPRESS:
5267 EndDialog(hDlg, ERI_SUPPRESS);
5268 break;
5269 case IDC_BREAK:
5270 EndDialog(hDlg, ERI_BREAK);
5271 break;
5272 case IDC_EXIT:
5273 EndDialog(hDlg, ERI_EXIT);
5274 break;
5275
5276 default:
5277 break;
5278 }
5279 }
5280
5281
5282 static void dlg_OnSysCommand(HWND hDlg, UINT cmd, int UNUSED(x), int UNUSED(y))
5283 {
5284 switch(cmd & 0xFFF0) // NB: lower 4 bits are reserved
5285 {
5286 // [X] clicked -> close dialog (doesn't happen automatically)
5287 case SC_CLOSE:
5288 EndDialog(hDlg, 0);
5289 break;
5290
5291 default:
5292 break;
5293 }
5294 }
5295
5296
5297 static INT_PTR CALLBACK dlg_OnMessage(HWND hDlg, unsigned int msg, WPARAM wParam, LPARAM lParam)
5298 {
5299 switch(msg)
5300 {
5301 case WM_INITDIALOG:
5302 return HANDLE_WM_INITDIALOG(hDlg, wParam, lParam, dlg_OnInitDialog);
5303
5304 case WM_SYSCOMMAND:
5305 return HANDLE_WM_SYSCOMMAND(hDlg, wParam, lParam, dlg_OnSysCommand);
5306
5307 case WM_COMMAND:
5308 return HANDLE_WM_COMMAND(hDlg, wParam, lParam, dlg_OnCommand);
5309
5310 case WM_MOVE:
5311 return HANDLE_WM_MOVE(hDlg, wParam, lParam, dlg_OnMove);
5312
5313 case WM_GETMINMAXINFO:
5314 return HANDLE_WM_GETMINMAXINFO(hDlg, wParam, lParam, dlg_OnGetMinMaxInfo);
5315
5316 case WM_SIZE:
5317 return HANDLE_WM_SIZE(hDlg, wParam, lParam, dlg_OnSize);
5318
5319 default:
5320 // we didn't process the message; caller will perform default action.
5321 return FALSE;
5322 }
5323 }
5324
5325
5326 ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags)
5327 {
5328 // note: other threads might still be running, crash and take down the
5329 // process before we have a chance to display this error message.
5330 // ideally we would suspend them all and resume when finished; however,
5331 // they may be holding system-wide locks (e.g. heap or loader) that
5332 // are potentially needed by DialogBoxParam. in that case, deadlock
5333 // would result; this is much worse than a crash because no error
5334 // at all is displayed to the end-user. therefore, do nothing here.
5335
5336 // temporarily remove any pending quit message from the queue because
5337 // it would prevent the dialog from being displayed (DialogBoxParam
5338 // returns IDOK without doing anything). will be restored below.
5339 // notes:
5340 // - this isn't only relevant at exit - Windows also posts one if
5341 // window init fails. therefore, it is important that errors can be
5342 // displayed regardless.
5343 // - by passing hWnd=0, we check all windows belonging to the current
5344 // thread. there is no reason to use hWndParent below.
5345 MSG msg;
5346 const BOOL isQuitPending = PeekMessage(&msg, 0, WM_QUIT, WM_QUIT, PM_REMOVE);
5347
5348 const HINSTANCE hInstance = wutil_LibModuleHandle();
5349 LPCWSTR lpTemplateName = MAKEINTRESOURCEW(IDD_DIALOG1);
5350 const DialogParams params = { text, flags };
5351 // get the enclosing app's window handle. we can't just pass 0 or
5352 // the desktop window because the dialog must be modal (if the app
5353 // continues running, it may crash and take down the process before
5354 // we've managed to show the dialog).
5355 const HWND hWndParent = wutil_AppWindow();
5356
5357 INT_PTR ret = DialogBoxParamW(hInstance, lpTemplateName, hWndParent, dlg_OnMessage, (LPARAM)¶ms);
5358
5359 if(isQuitPending)
5360 PostQuitMessage((int)msg.wParam);
5361
5362 // failed; warn user and make sure we return an ErrorReactionInternal.
5363 if(ret == 0 || ret == -1)
5364 {
5365 debug_DisplayMessage(L"Error", L"Unable to display detailed error dialog.");
5366 return ERI_CONTINUE;
5367 }
5368 return (ErrorReactionInternal)ret;
5369 }
5370
5371
5372 //-----------------------------------------------------------------------------
5373 // misc
5374 //-----------------------------------------------------------------------------
5375
5376 Status sys_StatusDescription(int user_err, wchar_t* buf, size_t max_chars)
5377 {
5378 // validate user_err - Win32 doesn't have negative error numbers
5379 if(user_err < 0)
5380 return ERR::FAIL; // NOWARN
5381
5382 const DWORD err = user_err? (DWORD)user_err : GetLastError();
5383
5384 // no one likes to see "The operation completed successfully" in
5385 // error messages, so return more descriptive text instead.
5386 if(err == 0)
5387 {
5388 wcscpy_s(buf, max_chars, L"0 (no error code was set)");
5389 return INFO::OK;
5390 }
5391
5392 wchar_t message[400];
5393 {
5394 const LPCVOID source = 0; // ignored (we're not using FROM_HMODULE etc.)
5395 const DWORD lang_id = 0; // look for neutral, then current locale
5396 va_list* args = 0; // we don't care about "inserts"
5397 const DWORD charsWritten = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, source, err, lang_id, message, (DWORD)ARRAY_SIZE(message), args);
5398 if(!charsWritten)
5399 WARN_RETURN(ERR::FAIL);
5400 ENSURE(charsWritten < max_chars);
5401 if(message[charsWritten-1] == '\n')
5402 message[charsWritten-1] = '\0';
5403 if(message[charsWritten-2] == '\r')
5404 message[charsWritten-2] = '\0';
5405 }
5406
5407 const int charsWritten = swprintf_s(buf, max_chars, L"%d (%ls)", err, message);
5408 ENSURE(charsWritten != -1);
5409 return INFO::OK;
5410 }
5411
5412
5413 static Status GetModulePathname(HMODULE hModule, OsPath& pathname)
5414 {
5415 wchar_t pathnameBuf[32768]; // NTFS limit
5416 const DWORD length = (DWORD)ARRAY_SIZE(pathnameBuf);
5417 const DWORD charsWritten = GetModuleFileNameW(hModule, pathnameBuf, length);
5418 if(charsWritten == 0) // failed
5419 WARN_RETURN(StatusFromWin());
5420 ENSURE(charsWritten < length); // why would the above buffer ever be exceeded?
5421 pathname = pathnameBuf;
5422 return INFO::OK;
5423 }
5424
5425
5426 Status sys_get_module_filename(void* addr, OsPath& pathname)
5427 {
5428 MEMORY_BASIC_INFORMATION mbi;
5429 const SIZE_T bytesWritten = VirtualQuery(addr, &mbi, sizeof(mbi));
5430 if(!bytesWritten)
5431 WARN_RETURN(StatusFromWin());
5432 ENSURE(bytesWritten >= sizeof(mbi));
5433 return GetModulePathname((HMODULE)mbi.AllocationBase, pathname);
5434 }
5435
5436
5437 OsPath sys_ExecutablePathname()
5438 {
5439 WinScopedPreserveLastError s;
5440 OsPath pathname;
5441 ENSURE(GetModulePathname(0, pathname) == INFO::OK);
5442 return pathname;
5443 }
5444
5445
5446 std::wstring sys_get_user_name()
5447 {
5448 wchar_t usernameBuf[256];
5449 DWORD size = ARRAY_SIZE(usernameBuf);
5450 if(!GetUserNameW(usernameBuf, &size))
5451 return L"";
5452 return usernameBuf;
5453 }
5454
5455
5456 // callback for shell directory picker: used to set starting directory
5457 // (for user convenience).
5458 static int CALLBACK BrowseCallback(HWND hWnd, unsigned int msg, LPARAM UNUSED(lParam), LPARAM lpData)
5459 {
5460 if(msg == BFFM_INITIALIZED)
5461 {
5462 const WPARAM wParam = TRUE; // lpData is a Unicode string, not PIDL.
5463 // (MSDN: the return values for both of these BFFM_ notifications are ignored)
5464 (void)SendMessage(hWnd, BFFM_SETSELECTIONW, wParam, lpData);
5465 }
5466
5467 return 0;
5468 }
5469
5470 Status sys_pick_directory(OsPath& path)
5471 {
5472 // (must not use multi-threaded apartment due to BIF_NEWDIALOGSTYLE)
5473 const HRESULT hr = CoInitialize(0);
5474 ENSURE(hr == S_OK || hr == S_FALSE); // S_FALSE == already initialized
5475
5476 // note: bi.pszDisplayName isn't the full path, so it isn't of any use.
5477 BROWSEINFOW bi;
5478 memset(&bi, 0, sizeof(bi));
5479 bi.ulFlags = BIF_RETURNONLYFSDIRS|BIF_NEWDIALOGSTYLE|BIF_NONEWFOLDERBUTTON;
5480 // for setting starting directory:
5481 bi.lpfn = (BFFCALLBACK)BrowseCallback;
5482 const Path::String initialPath = OsString(path); // NB: BFFM_SETSELECTIONW can't deal with '/' separators
5483 bi.lParam = (LPARAM)initialPath.c_str();
5484 const LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
5485 if(!pidl) // user canceled
5486 return INFO::SKIPPED;
5487
5488 // translate ITEMIDLIST to string
5489 wchar_t pathBuf[MAX_PATH]; // mandated by SHGetPathFromIDListW
5490 const BOOL ok = SHGetPathFromIDListW(pidl, pathBuf);
5491
5492 // free the ITEMIDLIST
5493 CoTaskMemFree(pidl);
5494
5495 if(ok == TRUE)
5496 {
5497 path = pathBuf;
5498 return INFO::OK;
5499 }
5500
5501 // Balance call to CoInitialize, which must have been successful
5502 CoUninitialize();
5503
5504 WARN_RETURN(StatusFromWin());
5505 }
5506
5507
5508 Status sys_open_url(const std::string& url)
5509 {
5510 HINSTANCE r = ShellExecuteA(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL);
5511 if ((int)(intptr_t)r > 32)
5512 return INFO::OK;
5513
5514 WARN_RETURN(ERR::FAIL);
5515 }
5516
5517
5518 Status sys_generate_random_bytes(u8* buffer, size_t size)
5519 {
5520 HCRYPTPROV hCryptProv = 0;
5521 if(!CryptAcquireContext(&hCryptProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
5522 WARN_RETURN(StatusFromWin());
5523
5524 memset(buffer, 0, size);
5525 if(!CryptGenRandom(hCryptProv, (DWORD)size, (BYTE*)buffer))
5526 WARN_RETURN(StatusFromWin());
5527
5528 if(!CryptReleaseContext(hCryptProv, 0))
5529 WARN_RETURN(StatusFromWin());
5530
5531 return INFO::OK;
5532 }
5533
5534
5535 #if CONFIG_ENABLE_BOOST
5536
5537 /*
5538 * Given a string of the form
5539 * "example.com:80"
5540 * or
5541 * "ftp=ftp.example.com:80;http=example.com:80;https=example.com:80"
5542 * separated by semicolons or whitespace,
5543 * return the string "example.com:80".
5544 */
5545 static std::wstring parse_proxy(const std::wstring& input)
5546 {
5547 if(input.find('=') == input.npos)
5548 return input;
5549
5550 std::vector<std::wstring> parts;
5551 split(parts, input, boost::algorithm::is_any_of("; \t\r\n"), boost::algorithm::token_compress_on);
5552
5553 for(size_t i = 0; i < parts.size(); ++i)
5554 if(boost::algorithm::starts_with(parts[i], "http="))
5555 return parts[i].substr(5);
5556
5557 // If we got this far, proxies were only set for non-HTTP protocols
5558 return L"";
5559 }
5560
5561 Status sys_get_proxy_config(const std::wstring& url, std::wstring& proxy)
5562 {
5563 WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
5564 memset(&autoProxyOptions, 0, sizeof(autoProxyOptions));
5565 autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
5566 autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
5567 autoProxyOptions.fAutoLogonIfChallenged = TRUE;
5568
5569 WINHTTP_PROXY_INFO proxyInfo;
5570 memset(&proxyInfo, 0, sizeof(proxyInfo));
5571
5572 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieConfig;
5573 memset(&ieConfig, 0, sizeof(ieConfig));
5574
5575 HINTERNET hSession = NULL;
5576
5577 Status err = INFO::SKIPPED;
5578
5579 bool useAutoDetect;
5580
5581 if(WinHttpGetIEProxyConfigForCurrentUser(&ieConfig))
5582 {
5583 if(ieConfig.lpszAutoConfigUrl)
5584 {
5585 // Use explicit auto-config script if specified
5586 useAutoDetect = true;
5587 autoProxyOptions.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL;
5588 autoProxyOptions.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrl;
5589 }
5590 else
5591 {
5592 // Use auto-discovery if enabled
5593 useAutoDetect = (ieConfig.fAutoDetect == TRUE);
5594 }
5595 }
5596 else
5597 {
5598 // Can't find IE config settings - fall back to auto-discovery
5599 useAutoDetect = true;
5600 }
5601
5602 if(useAutoDetect)
5603 {
5604 hSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
5605 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
5606
5607 if(hSession && WinHttpGetProxyForUrl(hSession, url.c_str(), &autoProxyOptions, &proxyInfo) && proxyInfo.lpszProxy)
5608 {
5609 proxy = parse_proxy(proxyInfo.lpszProxy);
5610 if(!proxy.empty())
5611 {
5612 err = INFO::OK;
5613 goto done;
5614 }
5615 }
5616 }
5617
5618 // No valid auto-config; try explicit proxy instead
5619 if(ieConfig.lpszProxy)
5620 {
5621 proxy = parse_proxy(ieConfig.lpszProxy);
5622 if(!proxy.empty())
5623 {
5624 err = INFO::OK;
5625 goto done;
5626 }
5627 }
5628
5629 done:
5630 if(ieConfig.lpszProxy)
5631 GlobalFree(ieConfig.lpszProxy);
5632 if(ieConfig.lpszProxyBypass)
5633 GlobalFree(ieConfig.lpszProxyBypass);
5634 if(ieConfig.lpszAutoConfigUrl)
5635 GlobalFree(ieConfig.lpszAutoConfigUrl);
5636 if(proxyInfo.lpszProxy)
5637 GlobalFree(proxyInfo.lpszProxy);
5638 if(proxyInfo.lpszProxyBypass)
5639 GlobalFree(proxyInfo.lpszProxyBypass);
5640 if(hSession)
5641 WinHttpCloseHandle(hSession);
5642
5643 return err;
5644 }
5645
5646 #endif
5647
5648 FILE* sys_OpenFile(const OsPath& pathname, const char* mode)
5649 {
5650 FILE* f = 0;
5651 const std::wstring wmode(mode, mode+strlen(mode));
5652 (void)_wfopen_s(&f, OsString(pathname).c_str(), wmode.c_str());
5653 return f;
5654 }
5655Index: source/lib/sysdep/os/win/wutil.cpp
5656===================================================================
5657--- source/lib/sysdep/os/win/wutil.cpp (revision 23275)
5658+++ source/lib/sysdep/os/win/wutil.cpp (working copy)
5659@@ -1,580 +1,580 @@
5660 /* Copyright (C) 2015 Wildfire Games.
5661 *
5662 * Permission is hereby granted, free of charge, to any person obtaining
5663 * a copy of this software and associated documentation files (the
5664 * "Software"), to deal in the Software without restriction, including
5665 * without limitation the rights to use, copy, modify, merge, publish,
5666 * distribute, sublicense, and/or sell copies of the Software, and to
5667 * permit persons to whom the Software is furnished to do so, subject to
5668 * the following conditions:
5669 *
5670 * The above copyright notice and this permission notice shall be included
5671 * in all copies or substantial portions of the Software.
5672 *
5673 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
5674 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
5675 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
5676 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
5677 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
5678 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
5679 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5680 */
5681
5682 /*
5683 * various Windows-specific utilities
5684 */
5685
5686 #include "precompiled.h"
5687 #include "lib/sysdep/os/win/wutil.h"
5688
5689 #include <stdio.h>
5690 #include <stdlib.h> // __argc
5691
5692 #include "lib/file/file.h"
5693 #include "lib/posix/posix.h"
5694 #include "lib/sysdep/sysdep.h"
5695 #include "lib/sysdep/os/win/win.h"
5696 #include "lib/sysdep/os/win/wdbg.h" // wdbg_assert
5697 #include "lib/sysdep/os/win/winit.h"
5698
5699 #include <shlobj.h> // SHGetFolderPath
5700
5701
5702 WINIT_REGISTER_EARLY_INIT(wutil_Init);
5703 WINIT_REGISTER_LATE_SHUTDOWN(wutil_Shutdown);
5704
5705
5706 //-----------------------------------------------------------------------------
5707 // safe allocator
5708
5709 // may be used independently of libc malloc
5710 // (in particular, before _cinit and while calling static dtors).
5711 // used by wpthread critical section code.
5712
5713 void* wutil_Allocate(size_t size)
5714 {
5715 const DWORD flags = HEAP_ZERO_MEMORY;
5716 return HeapAlloc(GetProcessHeap(), flags, size);
5717 }
5718
5719 void wutil_Free(void* p)
5720 {
5721 const DWORD flags = 0;
5722 HeapFree(GetProcessHeap(), flags, p);
5723 }
5724
5725
5726 //-----------------------------------------------------------------------------
5727 // locks
5728
5729 // several init functions are before called before _cinit.
5730 // POSIX static mutex init may not have been done by then,
5731 // so we need our own lightweight functions.
5732
5733 static CRITICAL_SECTION cs[NUM_CS];
5734 static bool cs_valid;
5735
5736 void wutil_Lock(WinLockId id)
5737 {
5738 if(!cs_valid)
5739 return;
5740 EnterCriticalSection(&cs[id]);
5741 }
5742
5743 void wutil_Unlock(WinLockId id)
5744 {
5745 if(!cs_valid)
5746 return;
5747 LeaveCriticalSection(&cs[id]);
5748 }
5749
5750 bool wutil_IsLocked(WinLockId id)
5751 {
5752 if(!cs_valid)
5753 return false;
5754 const BOOL successfullyEntered = TryEnterCriticalSection(&cs[id]);
5755 if(!successfullyEntered)
5756 return true; // still locked
5757 LeaveCriticalSection(&cs[id]);
5758 return false; // probably not locked
5759 }
5760
5761
5762 static void InitLocks()
5763 {
5764 for(int i = 0; i < NUM_CS; i++)
5765 InitializeCriticalSection(&cs[i]);
5766
5767 cs_valid = true;
5768 }
5769
5770 static void ShutdownLocks()
5771 {
5772 cs_valid = false;
5773
5774 for(int i = 0; i < NUM_CS; i++)
5775 DeleteCriticalSection(&cs[i]);
5776 memset(cs, 0, sizeof(cs));
5777 }
5778
5779
5780 //-----------------------------------------------------------------------------
5781 // error codes
5782
5783 // only call after a Win32 function indicates failure.
5784 Status StatusFromWin()
5785 {
5786 switch(GetLastError())
5787 {
5788 case ERROR_BUSY:
5789 case WAIT_TIMEOUT:
5790 return ERR::AGAIN;
5791 case ERROR_OPERATION_ABORTED:
5792 return ERR::ABORTED;
5793
5794 case ERROR_INVALID_HANDLE:
5795 return ERR::INVALID_HANDLE;
5796 case ERROR_INSUFFICIENT_BUFFER:
5797 return ERR::INVALID_SIZE;
5798 case ERROR_INVALID_PARAMETER:
5799 case ERROR_BAD_ARGUMENTS:
5800 return ERR::INVALID_PARAM;
5801
5802 case ERROR_OUTOFMEMORY:
5803 case ERROR_NOT_ENOUGH_MEMORY:
5804 return ERR::NO_MEM;
5805 case ERROR_NOT_SUPPORTED:
5806 case ERROR_CALL_NOT_IMPLEMENTED:
5807 case ERROR_PROC_NOT_FOUND:
5808 return ERR::NOT_SUPPORTED;
5809
5810 case ERROR_FILE_NOT_FOUND:
5811 case ERROR_PATH_NOT_FOUND:
5812 return ERR::FILE_NOT_FOUND;
5813 case ERROR_ACCESS_DENIED:
5814 return ERR::FILE_ACCESS;
5815
5816 default:
5817 return ERR::FAIL;
5818 }
5819 }
5820
5821
5822 //-----------------------------------------------------------------------------
5823 // command line
5824
5825 // copy of GetCommandLine string. will be tokenized and then referenced by
5826 // the argv pointers.
5827 static wchar_t* argvContents;
5828
5829 int s_argc = 0;
5830 wchar_t** s_argv = 0;
5831
5832 static void ReadCommandLine()
5833 {
5834 const wchar_t* commandLine = GetCommandLineW();
5835 // (this changes as quotation marks are removed)
5836 size_t numChars = wcslen(commandLine);
5837 argvContents = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, (numChars+1)*sizeof(wchar_t));
5838 wcscpy_s(argvContents, numChars+1, commandLine);
5839
5840 // first pass: tokenize string and count number of arguments
5841 bool ignoreSpace = false;
5842 for(size_t i = 0; i < numChars; i++)
5843 {
5844 switch(argvContents[i])
5845 {
5846 case '"':
5847 ignoreSpace = !ignoreSpace;
5848 // strip the " character
5849 memmove(argvContents+i, argvContents+i+1, (numChars-i)*sizeof(wchar_t));
5850 numChars--;
5851 i--;
5852 break;
5853
5854 case ' ':
5855 if(!ignoreSpace)
5856 {
5857 argvContents[i] = '\0';
5858 s_argc++;
5859 }
5860 break;
5861 }
5862 }
5863 s_argc++;
5864
5865 // have argv entries point into the tokenized string
5866 s_argv = (wchar_t**)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, s_argc*sizeof(wchar_t*));
5867 wchar_t* nextArg = argvContents;
5868 for(int i = 0; i < s_argc; i++)
5869 {
5870 s_argv[i] = nextArg;
5871 nextArg += wcslen(nextArg)+1;
5872 }
5873 }
5874
5875
5876 int wutil_argc()
5877 {
5878 return s_argc;
5879 }
5880
5881 wchar_t** wutil_argv()
5882 {
5883 ENSURE(s_argv);
5884 return s_argv;
5885 }
5886
5887
5888 static void FreeCommandLine()
5889 {
5890 HeapFree(GetProcessHeap(), 0, s_argv);
5891 HeapFree(GetProcessHeap(), 0, argvContents);
5892 }
5893
5894
5895 bool wutil_HasCommandLineArgument(const wchar_t* arg)
5896 {
5897 for(int i = 0; i < s_argc; i++)
5898 {
5899 if(!wcscmp(s_argv[i], arg))
5900 return true;
5901 }
5902
5903 return false;
5904 }
5905
5906
5907 //-----------------------------------------------------------------------------
5908 // directories
5909
5910 // (NB: wutil_Init is called before static ctors => use placement new)
5911 static OsPath* systemPath;
5912 static OsPath* executablePath;
5913 static OsPath* localAppdataPath;
5914 static OsPath* roamingAppdataPath;
5915 static OsPath* personalPath;
5916
5917 const OsPath& wutil_SystemPath()
5918 {
5919 return *systemPath;
5920 }
5921
5922 const OsPath& wutil_ExecutablePath()
5923 {
5924 return *executablePath;
5925 }
5926
5927 const OsPath& wutil_LocalAppdataPath()
5928 {
5929 return *localAppdataPath;
5930 }
5931
5932 const OsPath& wutil_RoamingAppdataPath()
5933 {
5934 return *roamingAppdataPath;
5935 }
5936
5937 const OsPath& wutil_PersonalPath()
5938 {
5939 return *personalPath;
5940 }
5941
5942 // Helper to avoid duplicating this setup
5943 static OsPath* GetFolderPath(int csidl)
5944 {
5945 HWND hwnd = 0; // ignored unless a dial-up connection is needed to access the folder
5946 HANDLE token = 0;
5947 wchar_t path[MAX_PATH]; // mandated by SHGetFolderPathW
5948 const HRESULT ret = SHGetFolderPathW(hwnd, csidl, token, 0, path);
5949 if (!SUCCEEDED(ret))
5950 {
5951 debug_printf("SHGetFolderPathW failed with HRESULT = 0x%08lx for csidl = 0x%04x\n", ret, csidl);
5952 debug_warn("SHGetFolderPathW failed (see debug output)");
5953 }
5954 if(GetLastError() == ERROR_NO_TOKEN) // avoid polluting last error
5955 SetLastError(0);
5956 return new(wutil_Allocate(sizeof(OsPath))) OsPath(path);
5957 }
5958
5959 static void GetDirectories()
5960 {
5961 WinScopedPreserveLastError s;
5962
5963 // system directory
5964 {
5965 const UINT length = GetSystemDirectoryW(0, 0);
5966 ENSURE(length != 0);
5967 std::wstring path(length, '\0');
5968 const UINT charsWritten = GetSystemDirectoryW(&path[0], length);
5969 ENSURE(charsWritten == length-1);
5970 systemPath = new(wutil_Allocate(sizeof(OsPath))) OsPath(path);
5971 }
5972
5973 // executable's directory
5974 executablePath = new(wutil_Allocate(sizeof(OsPath))) OsPath(sys_ExecutablePathname().Parent());
5975
5976 // roaming application data
5977 roamingAppdataPath = GetFolderPath(CSIDL_APPDATA);
5978
5979 // local application data
5980 localAppdataPath = GetFolderPath(CSIDL_LOCAL_APPDATA);
5981
5982 // my documents
5983 personalPath = GetFolderPath(CSIDL_PERSONAL);
5984 }
5985
5986
5987 static void FreeDirectories()
5988 {
5989 systemPath->~OsPath();
5990 wutil_Free(systemPath);
5991 executablePath->~OsPath();
5992 wutil_Free(executablePath);
5993 localAppdataPath->~OsPath();
5994 wutil_Free(localAppdataPath);
5995 roamingAppdataPath->~OsPath();
5996 wutil_Free(roamingAppdataPath);
5997 personalPath->~OsPath();
5998 wutil_Free(personalPath);
5999 }
6000
6001
6002 //-----------------------------------------------------------------------------
6003 // user32 fix
6004
6005 // HACK: make sure a reference to user32 is held, even if someone
6006 // decides to delay-load it. this fixes bug #66, which was the
6007 // Win32 mouse cursor (set via user32!SetCursor) appearing as a
6008 // black 32x32(?) rectangle. the underlying cause was as follows:
6009 // powrprof.dll was the first client of user32, causing it to be
6010 // loaded. after we were finished with powrprof, we freed it, in turn
6011 // causing user32 to unload. later code would then reload user32,
6012 // which apparently terminally confused the cursor implementation.
6013 //
6014 // since we hold a reference here, user32 will never unload.
6015 // of course, the benefits of delay-loading are lost for this DLL,
6016 // but that is unavoidable. it is safer to force loading it, rather
6017 // than documenting the problem and asking it not be delay-loaded.
6018 static HMODULE hUser32Dll;
6019
6020 static void ForciblyLoadUser32Dll()
6021 {
6022 hUser32Dll = LoadLibraryW(L"user32.dll");
6023 }
6024
6025 // avoids Boundschecker warning
6026 static void FreeUser32Dll()
6027 {
6028 FreeLibrary(hUser32Dll);
6029 }
6030
6031
6032 //-----------------------------------------------------------------------------
6033 // memory
6034
6035 static void EnableLowFragmentationHeap()
6036 {
6037 if(IsDebuggerPresent())
6038 {
6039 // and the debug heap isn't explicitly disabled,
6040 char* var = getenv("_NO_DEBUG_HEAP");
6041 if(!var || var[0] != '1')
6042 return; // we can't enable the LFH
6043 }
6044
6045 #if WINVER >= 0x0501
6046 WUTIL_FUNC(pHeapSetInformation, BOOL, (HANDLE, HEAP_INFORMATION_CLASS, void*, size_t));
6047 WUTIL_IMPORT_KERNEL32(HeapSetInformation, pHeapSetInformation);
6048 if(pHeapSetInformation)
6049 {
6050 ULONG flags = 2; // enable LFH
6051 pHeapSetInformation(GetProcessHeap(), HeapCompatibilityInformation, &flags, sizeof(flags));
6052 }
6053 #endif // #if WINVER >= 0x0501
6054 }
6055
6056
6057 //-----------------------------------------------------------------------------
6058 // Wow64
6059
6060 // Wow64 'helpfully' redirects all 32-bit apps' accesses of
6061 // %windir%\\system32\\drivers to %windir%\\system32\\drivers\\SysWOW64.
6062 // that's bad, because the actual drivers are not in the subdirectory. to
6063 // work around this, provide for temporarily disabling redirection.
6064
6065 static WUTIL_FUNC(pIsWow64Process, BOOL, (HANDLE, PBOOL));
6066 static WUTIL_FUNC(pWow64DisableWow64FsRedirection, BOOL, (PVOID*));
6067 static WUTIL_FUNC(pWow64RevertWow64FsRedirection, BOOL, (PVOID));
6068
6069 static bool isWow64;
6070
6071 static void ImportWow64Functions()
6072 {
6073 WUTIL_IMPORT_KERNEL32(IsWow64Process, pIsWow64Process);
6074 WUTIL_IMPORT_KERNEL32(Wow64DisableWow64FsRedirection, pWow64DisableWow64FsRedirection);
6075 WUTIL_IMPORT_KERNEL32(Wow64RevertWow64FsRedirection, pWow64RevertWow64FsRedirection);
6076 }
6077
6078 static void DetectWow64()
6079 {
6080 // function not found => running on 32-bit Windows
6081 if(!pIsWow64Process)
6082 {
6083 isWow64 = false;
6084 return;
6085 }
6086
6087 BOOL isWow64Process = FALSE;
6088 const BOOL ok = pIsWow64Process(GetCurrentProcess(), &isWow64Process);
6089 WARN_IF_FALSE(ok);
6090 isWow64 = (isWow64Process == TRUE);
6091 }
6092
6093 bool wutil_IsWow64()
6094 {
6095 return isWow64;
6096 }
6097
6098
6099 WinScopedDisableWow64Redirection::WinScopedDisableWow64Redirection()
6100 {
6101 // note: don't just check if the function pointers are valid. 32-bit
6102 // Vista includes them but isn't running Wow64, so calling the functions
6103 // would fail. since we have to check if actually on Wow64, there's no
6104 // more need to verify the pointers (their existence is implied).
6105 if(!wutil_IsWow64())
6106 return;
6107 const BOOL ok = pWow64DisableWow64FsRedirection(&m_wasRedirectionEnabled);
6108 WARN_IF_FALSE(ok);
6109 }
6110
6111 WinScopedDisableWow64Redirection::~WinScopedDisableWow64Redirection()
6112 {
6113 if(!wutil_IsWow64())
6114 return;
6115 const BOOL ok = pWow64RevertWow64FsRedirection(m_wasRedirectionEnabled);
6116 WARN_IF_FALSE(ok);
6117 }
6118
6119
6120 //-----------------------------------------------------------------------------
6121
6122 Status wutil_SetPrivilege(const wchar_t* privilege, bool enable)
6123 {
6124 WinScopedPreserveLastError s;
6125
6126 HANDLE hToken;
6127 if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken))
6128 return ERR::_1;
6129
6130 TOKEN_PRIVILEGES tp;
6131 if (!LookupPrivilegeValueW(NULL, privilege, &tp.Privileges[0].Luid))
6132 return ERR::_2;
6133 tp.PrivilegeCount = 1;
6134 tp.Privileges[0].Attributes = enable? SE_PRIVILEGE_ENABLED : 0;
6135
6136 SetLastError(0);
6137 const BOOL ok = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, 0, 0);
6138 if(!ok || GetLastError() != 0)
6139 return ERR::_3;
6140
6141 WARN_IF_FALSE(CloseHandle(hToken));
6142 return INFO::OK;
6143 }
6144
6145
6146 //-----------------------------------------------------------------------------
6147 // module handle
6148
6149 #ifndef LIB_STATIC_LINK
6150
6151 #include "lib/sysdep/os/win/wdll_main.h"
6152
6153 HMODULE wutil_LibModuleHandle()
6154 {
6155 HMODULE hModule;
6156 const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
6157- const BOOL ok = GetModuleHandleEx(flags, (LPCWSTR)&wutil_LibModuleHandle, &hModule);
6158+ const BOOL ok = GetModuleHandleEx(flags, (LPCSTR)&wutil_LibModuleHandle, &hModule);
6159 // (avoid ENSURE etc. because we're called from debug_DisplayError)
6160 wdbg_assert(ok);
6161 return hModule;
6162 }
6163
6164 #else
6165
6166 HMODULE wutil_LibModuleHandle()
6167 {
6168 return GetModuleHandle(0);
6169 }
6170
6171 #endif
6172
6173
6174
6175 //-----------------------------------------------------------------------------
6176 // find main window
6177
6178 // this is required by the error dialog and clipboard code.
6179 // note that calling from wutil_Init won't work, because the app will not
6180 // have created its window by then.
6181
6182 static HWND hAppWindow;
6183
6184 static BOOL CALLBACK FindAppWindowByPid(HWND hWnd, LPARAM UNUSED(lParam))
6185 {
6186 DWORD pid;
6187 DWORD tid = GetWindowThreadProcessId(hWnd, &pid);
6188 UNUSED2(tid);
6189
6190 if(pid == GetCurrentProcessId())
6191 hAppWindow = hWnd;
6192
6193 return TRUE; // keep calling
6194 }
6195
6196 HWND wutil_AppWindow()
6197 {
6198 if(!hAppWindow)
6199 {
6200 WARN_IF_FALSE(EnumWindows(FindAppWindowByPid, 0));
6201 // (hAppWindow may still be 0 if we haven't created a window yet)
6202 }
6203
6204 return hAppWindow;
6205 }
6206
6207
6208 //-----------------------------------------------------------------------------
6209
6210 static Status wutil_Init()
6211 {
6212 InitLocks();
6213
6214 ForciblyLoadUser32Dll();
6215
6216 EnableLowFragmentationHeap();
6217
6218 ReadCommandLine();
6219
6220 GetDirectories();
6221
6222 ImportWow64Functions();
6223 DetectWow64();
6224
6225 return INFO::OK;
6226 }
6227
6228
6229 static Status wutil_Shutdown()
6230 {
6231 FreeCommandLine();
6232
6233 FreeUser32Dll();
6234
6235 ShutdownLocks();
6236
6237 FreeDirectories();
6238
6239 return INFO::OK;
6240 }
6241Index: source/meson.build
6242===================================================================
6243--- source/meson.build (nonexistent)
6244+++ source/meson.build (working copy)
6245@@ -0,0 +1,1066 @@
6246+cpp_args = []
6247+link_with = []
6248+dependencies = [
6249+ dep_openal,
6250+ dep_sodium,
6251+ dep_boost,
6252+ dep_iconv,
6253+ dep_icu,
6254+ dep_curl,
6255+ dep_png,
6256+ dep_libxml2,
6257+ dep_sdl2,
6258+ dep_zlib,
6259+ dep_js,
6260+ dep_gl,
6261+ dep_fcollada,
6262+ dep_valgrind,
6263+ dep_cxxtest,
6264+ dep_tinygettext
6265+]
6266+
6267+if host_machine.system() == 'linux'
6268+ dependencies += dep_dl
6269+ cpp_args += '-DLINUX'
6270+endif
6271+
6272+if host_machine.system() == 'windows'
6273+ cpp_args += '-DUSING_PCH=1'
6274+endif
6275+
6276+if get_option('buildtype') == 'release'
6277+ cpp_args += '-DNDEBUG'
6278+else
6279+ cpp_args += '-D_DEBUG'
6280+endif
6281+
6282+if get_option('gles')
6283+ cpp_args += '-DCONFIG2_GLES=1'
6284+else
6285+ cpp_args += '-DCONFIG2_GLES=0'
6286+endif
6287+
6288+if get_option('audio')
6289+ cpp_args += '-DCONFIG2_AUDIO=1'
6290+else
6291+ cpp_args += '-DCONFIG2_AUDIO=0'
6292+endif
6293+
6294+if get_option('lobby')
6295+ cpp_args += '-DCONFIG2_LOBBY=1'
6296+else
6297+ cpp_args += '-DCONFIG2_LOBBY=0'
6298+endif
6299+
6300+if get_option('upnp')
6301+ cpp_args += '-DCONFIG2_MINIUPNPC=1'
6302+else
6303+ cpp_args += '-DCONFIG2_MINIUPNPC=0'
6304+endif
6305+
6306+if get_option('nvtt')
6307+ cpp_args += '-DCONFIG2_NVTT=1'
6308+ dependencies += dep_nvtt
6309+else
6310+ cpp_args += '-DCONFIG2_NVTT=0'
6311+endif
6312+
6313+collada_args = cpp_args
6314+collada_args += '-DLIB_STATIC_LINK=1 -DHAVE_ICONV_CONST=1 -DICONV_CONST=const -DLIBICONV_STATIC'
6315+collada_sources = [
6316+ 'collada/precompiled.cpp',
6317+ 'collada/CommonConvert.cpp',
6318+ 'collada/Decompose.cpp',
6319+ 'collada/DLL.cpp',
6320+ 'collada/GeomReindex.cpp',
6321+ 'collada/Maths.cpp',
6322+ 'collada/PMDConvert.cpp',
6323+ 'collada/PSAConvert.cpp',
6324+ 'collada/StdSkeletons.cpp',
6325+ 'collada/XMLFix.cpp',
6326+]
6327+collada_dependencies = [dep_fcollada, dep_libxml2, dep_iconv]
6328+collada = library(
6329+ 'Collada',
6330+ sources: collada_sources,
6331+ cpp_pch: 'collada/precompiled.h',
6332+ dependencies: collada_dependencies,
6333+ cpp_args: collada_args,
6334+)
6335+
6336+glooxwrapper_sources = [
6337+ 'lobby/glooxwrapper/glooxwrapper.cpp',
6338+]
6339+glooxwrapper_cpp_args = cpp_args
6340+glooxwrapper_dependencies = [dep_gloox, dep_boost]
6341+glooxwrapper = library(
6342+ 'glooxwrapper',
6343+ glooxwrapper_sources,
6344+ include_directories: [
6345+ 'pch/glooxwrapper',
6346+ ],
6347+ dependencies: glooxwrapper_dependencies,
6348+ cpp_args: glooxwrapper_cpp_args,
6349+)
6350+
6351+link_with+= glooxwrapper
6352+
6353+
6354+
6355+if get_option('lobby')
6356+
6357+ lobby_sources = [
6358+ 'lobby/scripting/GlooxScriptConversions.cpp',
6359+ 'lobby/Globals.cpp',
6360+ 'lobby/scripting/JSInterface_Lobby.cpp',
6361+ 'lobby/StanzaExtensions.cpp',
6362+ 'lobby/XmppClient.cpp',
6363+ 'pch/lobby/precompiled.cpp',
6364+ ]
6365+
6366+ lobby = static_library(
6367+ 'lobby',
6368+ lobby_sources,
6369+ include_directories: 'lib',
6370+ dependencies: [dep_boost, dep_js, dep_gloox, dep_tinygettext, dep_sodium, dep_icu, dep_iconv, dep_enet],
6371+ cpp_args: cpp_args,
6372+ )
6373+else
6374+ lobby_sources = [
6375+ 'lobby/Globals.cpp',
6376+ 'lobby/scripting/JSInterface_Lobby.cpp',
6377+ ]
6378+
6379+ lobby = static_library(
6380+ 'lobby',
6381+ lobby_sources,
6382+ include_directories: 'lib',
6383+ dependencies: [dep_js],
6384+ cpp_args: cpp_args,
6385+ )
6386+endif
6387+
6388+link_with += lobby
6389+
6390+sound_cpp_args = cpp_args
6391+sound_cpp_args += '-DLIB_STATIC_LINK=1'
6392+
6393+lowlevel_sources = []
6394+
6395+if get_option('audio')
6396+ sources = []
6397+ lowlevel_sources += 'lib/snd.cpp'
6398+
6399+ soundmanager_sources = [
6400+ 'soundmanager/data/ogg.cpp',
6401+ 'soundmanager/data/OggData.cpp',
6402+ 'soundmanager/data/SoundData.cpp',
6403+ 'soundmanager/items/CBufferItem.cpp',
6404+ 'soundmanager/items/CSoundBase.cpp',
6405+ 'soundmanager/items/CSoundItem.cpp',
6406+ 'soundmanager/items/CStreamItem.cpp',
6407+ 'soundmanager/scripting/JSInterface_Sound.cpp',
6408+ 'soundmanager/scripting/SoundGroup.cpp',
6409+ 'soundmanager/SoundManager.cpp',
6410+ ]
6411+
6412+ soundmanager = static_library(
6413+ 'soundmanager',
6414+ soundmanager_sources,
6415+ include_directories: 'lib',
6416+ dependencies: [dep_openal, dep_vorbis, dep_boost, dep_sdl2, dep_js],
6417+ cpp_args: cpp_args,
6418+ )
6419+
6420+else
6421+ soundmanager_sources = [
6422+ 'soundmanager/SoundManager.cpp',
6423+ 'soundmanager/scripting/JSInterface_Sound.cpp',
6424+ ]
6425+
6426+ soundmanager = static_library(
6427+ 'soundmanager',
6428+ soundmanager_sources,
6429+ include_directories: 'lib',
6430+ dependencies: [dep_sdl2, dep_js],
6431+ cpp_args: cpp_args,
6432+ )
6433+
6434+endif
6435+link_with += soundmanager
6436+
6437+encryption = static_library(
6438+ 'encryption',
6439+ ['third_party/encryption/pkcs5_pbkdf2.cpp'],
6440+ include_directories: 'lib',
6441+ dependencies: [dep_sodium, dep_boost],
6442+ cpp_args: cpp_args,
6443+)
6444+
6445+link_with += encryption
6446+
6447+network_cpp_args = cpp_args
6448+network_cpp_args += '-DLIB_STATIC_LINK=1'
6449+network_sources = [
6450+ 'network/fsm.cpp',
6451+ 'network/NetClient.cpp',
6452+ 'network/NetClientTurnManager.cpp',
6453+ 'network/NetFileTransfer.cpp',
6454+ 'network/NetHost.cpp',
6455+ 'network/NetMessage.cpp',
6456+ 'network/NetMessageSim.cpp',
6457+ 'network/NetServer.cpp',
6458+ 'network/NetServerTurnManager.cpp',
6459+ 'network/NetSession.cpp',
6460+ 'network/NetStats.cpp',
6461+ 'network/StunClient.cpp',
6462+ 'network/scripting/JSInterface_Network.cpp',
6463+]
6464+
6465+network_dependencies = [dep_boost,dep_enet, dep_js, dep_sdl2]
6466+
6467+if get_option('upnp')
6468+ network_dependencies += dep_miniupnpc # Not supported without
6469+endif
6470+
6471+network = static_library(
6472+ 'network',
6473+ network_sources,
6474+ include_directories: 'pch/network/',
6475+ dependencies: network_dependencies,
6476+ cpp_args: network_cpp_args,
6477+)
6478+
6479+link_with += network
6480+
6481+tinygettext_cpp_args = cpp_args
6482+tinygettext_cpp_args += '-DLIB_STATIC_LINK=1'
6483+# TODO: make sure we don’t want to use the normal gettext instead.
6484+tinygettext_sources = [
6485+ 'third_party/tinygettext/src/dictionary.cpp',
6486+ 'third_party/tinygettext/src/dictionary_manager.cpp',
6487+ 'third_party/tinygettext/src/iconv.cpp',
6488+ 'third_party/tinygettext/src/language.cpp',
6489+ 'third_party/tinygettext/src/log.cpp',
6490+ 'third_party/tinygettext/src/plural_forms.cpp',
6491+ 'third_party/tinygettext/src/po_parser.cpp',
6492+ 'third_party/tinygettext/src/tinygettext.cpp',
6493+ 'third_party/tinygettext/src/unix_file_system.cpp',
6494+]
6495+
6496+tinygettext = static_library(
6497+ 'tinygettext',
6498+ tinygettext_sources,
6499+ include_directories: 'pch/tinygettext/',
6500+ dependencies: [dep_tinygettext, dep_boost, dep_iconv],
6501+ cpp_args: tinygettext_cpp_args,
6502+)
6503+
6504+link_with += tinygettext
6505+
6506+
6507+lowlevel_sources += [
6508+ 'lib/allocators/arena.cpp',
6509+ 'lib/allocators/dynarray.cpp',
6510+ 'lib/allocators/freelist.cpp',
6511+ 'lib/allocators/headerless.cpp',
6512+ 'lib/allocators/page_aligned.cpp',
6513+ 'lib/allocators/pool.cpp',
6514+ 'lib/allocators/shared_ptr.cpp',
6515+ 'lib/allocators/unique_range.cpp',
6516+ 'lib/app_hooks.cpp',
6517+ 'lib/base32.cpp',
6518+ 'lib/bits.cpp',
6519+ 'lib/byte_order.cpp',
6520+ 'lib/debug.cpp',
6521+ 'lib/debug_stl.cpp',
6522+ 'lib/external_libraries/dbghelp.cpp',
6523+ 'lib/file/archive/archive.cpp',
6524+ 'lib/file/archive/archive_zip.cpp',
6525+ 'lib/file/archive/codec.cpp',
6526+ 'lib/file/archive/codec_zlib.cpp',
6527+ 'lib/file/archive/stream.cpp',
6528+ 'lib/file/common/file_loader.cpp',
6529+ 'lib/file/common/file_stats.cpp',
6530+ 'lib/file/common/real_directory.cpp',
6531+ 'lib/file/common/trace.cpp',
6532+ 'lib/file/file.cpp',
6533+ 'lib/file/file_system.cpp',
6534+ 'lib/file/io/io.cpp',
6535+ 'lib/file/io/write_buffer.cpp',
6536+ 'lib/file/vfs/vfs.cpp',
6537+ 'lib/file/vfs/vfs_lookup.cpp',
6538+ 'lib/file/vfs/vfs_path.cpp',
6539+ 'lib/file/vfs/vfs_populate.cpp',
6540+ 'lib/file/vfs/vfs_tree.cpp',
6541+ 'lib/file/vfs/vfs_util.cpp',
6542+ 'lib/fnv_hash.cpp',
6543+ 'lib/frequency_filter.cpp',
6544+ 'lib/input.cpp',
6545+ 'lib/lib.cpp',
6546+ 'lib/module_init.cpp',
6547+ 'lib/ogl.cpp',
6548+ 'lib/path.cpp',
6549+ 'lib/posix/posix.cpp',
6550+ 'lib/rand.cpp',
6551+ 'lib/regex.cpp',
6552+ 'lib/res/graphics/cursor.cpp',
6553+ 'lib/res/graphics/ogl_tex.cpp',
6554+ 'lib/res/h_mgr.cpp',
6555+ 'lib/secure_crt.cpp',
6556+ 'lib/status.cpp',
6557+ 'lib/svn_revision.cpp',
6558+
6559+ # TODO: system specific files, to sort.
6560+ #'lib/sysdep/cpu.cpp',
6561+ 'lib/sysdep/gfx.cpp',
6562+ 'lib/sysdep/os_cpu.cpp',
6563+ 'lib/sysdep/smbios.cpp',
6564+
6565+ 'lib/tex/tex_bmp.cpp',
6566+ 'lib/tex/tex_codec.cpp',
6567+ 'lib/tex/tex.cpp',
6568+ 'lib/tex/tex_dds.cpp',
6569+ 'lib/tex/tex_png.cpp',
6570+ 'lib/tex/tex_tga.cpp',
6571+ 'lib/timer.cpp',
6572+ 'lib/utf8.cpp',
6573+ 'lib/wsecure_crt.cpp',
6574+]
6575+
6576+if host_machine.cpu_family() == 'x86'
6577+ lowlevel_sources += [
6578+ 'lib/sysdep/arch/ia32/ia32.cpp',
6579+ ]
6580+elif host_machine.cpu_family() == 'x86_64'
6581+ lowlevel_sources += [
6582+ 'lib/sysdep/arch/amd64/amd64.cpp',
6583+ 'lib/sysdep/arch/x86_x64/apic.cpp',
6584+ 'lib/sysdep/arch/x86_x64/cache.cpp',
6585+ 'lib/sysdep/arch/x86_x64/topology.cpp',
6586+ 'lib/sysdep/arch/x86_x64/x86_x64.cpp',
6587+ ]
6588+elif host_machine.cpu_family() == 'arm'
6589+ lowlevel_sources += [
6590+ 'lib/sysdep/arch/arm/arm.cpp',
6591+ ]
6592+elif host_machine.cpu_family() == 'aarch64'
6593+ lowlevel_sources += [
6594+ 'lib/sysdep/arch/aarch64/aarch64.cpp',
6595+ ]
6596+endif
6597+
6598+if host_machine.system() == 'windows'
6599+ lowlevel_sources += [
6600+ 'lib/sysdep/acpi.cpp',
6601+ 'lib/sysdep/os/win/mahaf.cpp',
6602+ 'lib/sysdep/os/win/manifest.cpp',
6603+ 'lib/sysdep/os/win/wcpu.cpp',
6604+ 'lib/sysdep/os/win/wdbg.cpp',
6605+ 'lib/sysdep/os/win/wdbg_heap.cpp',
6606+ 'lib/sysdep/os/win/wdbg_sym.cpp',
6607+ 'lib/sysdep/os/win/wdir_watch.cpp',
6608+ 'lib/sysdep/os/win/wdll_delay_load.cpp',
6609+ 'lib/sysdep/os/win/wdll_ver.cpp',
6610+ 'lib/sysdep/os/win/wfirmware.cpp',
6611+ 'lib/sysdep/os/win/wgfx.cpp',
6612+ 'lib/sysdep/os/win/whrt/counter.cpp',
6613+ 'lib/sysdep/os/win/whrt/hpet.cpp',
6614+ 'lib/sysdep/os/win/whrt/pmt.cpp',
6615+ 'lib/sysdep/os/win/whrt/qpc.cpp',
6616+ 'lib/sysdep/os/win/whrt/tgt.cpp',
6617+ 'lib/sysdep/os/win/whrt/tsc.cpp',
6618+ 'lib/sysdep/os/win/whrt/whrt.cpp',
6619+ 'lib/sysdep/os/win/winit.cpp',
6620+ 'lib/sysdep/os/win/wiocp.cpp',
6621+ 'lib/sysdep/os/win/wnuma.cpp',
6622+ 'lib/sysdep/os/win/wposix/waio.cpp',
6623+ 'lib/sysdep/os/win/wposix/wdlfcn.cpp',
6624+ 'lib/sysdep/os/win/wposix/wfilesystem.cpp',
6625+ 'lib/sysdep/os/win/wposix/wmman.cpp',
6626+ 'lib/sysdep/os/win/wposix/wposix.cpp',
6627+ 'lib/sysdep/os/win/wposix/wpthread.cpp',
6628+ 'lib/sysdep/os/win/wposix/wtime.cpp',
6629+ 'lib/sysdep/os/win/wposix/wutsname.cpp',
6630+ 'lib/sysdep/os/win/wprofiler.cpp',
6631+ 'lib/sysdep/os/win/wseh.cpp',
6632+ 'lib/sysdep/os/win/wstartup.cpp',
6633+ 'lib/sysdep/os/win/wsysdep.cpp',
6634+ 'lib/sysdep/os/win/wutil.cpp',
6635+ 'lib/sysdep/os/win/wversion.cpp',
6636+ 'lib/sysdep/os/win/wvm.cpp',
6637+ 'lib/sysdep/rtl/msc/msc.cpp',
6638+ ]
6639+ if host_machine.cpu_family() == 'x86_64'
6640+ lowlevel_sources += [
6641+ 'lib/sysdep/arch/x86_x64/msr.cpp',
6642+ ]
6643+ endif
6644+elif host_machine.system() == 'darwin'
6645+ lowlevel_sources += [
6646+ 'lib/sysdep/os/osx/dir_watch.cpp',
6647+ 'lib/sysdep/os/osx/ocpu.cpp',
6648+ 'lib/sysdep/os/osx/odbg.cpp',
6649+ 'lib/sysdep/os/osx/osx.cpp',
6650+ ]
6651+elif host_machine.system() == 'linux'
6652+ lowlevel_sources += [
6653+ 'lib/sysdep/os/linux/dir_watch_inotify.cpp',
6654+ 'lib/sysdep/os/linux/lcpu.cpp',
6655+ 'lib/sysdep/os/linux/ldbg.cpp',
6656+ 'lib/sysdep/os/linux/linux.cpp',
6657+ ]
6658+elif host_machine.system() == 'android'
6659+ lowlevel_sources += [
6660+ 'lib/sysdep/os/android/android.cpp',
6661+ ]
6662+elif ['openbsd', 'netbsd', 'freebsd', 'gnu/kfreebsd', 'dragonfly'].contains(host_machine.system())
6663+ lowlevel_sources += [
6664+ 'lib/sysdep/os/bsd/bcpu.cpp',
6665+ 'lib/sysdep/os/bsd/bdbg.cpp',
6666+ 'lib/sysdep/os/bsd/bsd.cpp',
6667+ 'lib/sysdep/os/bsd/dir_watch.cpp',
6668+ ]
6669+endif
6670+
6671+if host_machine.system() != 'windows'
6672+ lowlevel_sources += [
6673+ 'lib/sysdep/os/unix/udbg.cpp',
6674+ 'lib/sysdep/os/unix/ufilesystem.cpp',
6675+ 'lib/sysdep/os/unix/unix.cpp',
6676+ 'lib/sysdep/os/unix/unix_executable_pathname.cpp',
6677+ 'lib/sysdep/os/unix/unuma.cpp',
6678+ 'lib/sysdep/os/unix/uvm.cpp',
6679+ 'lib/sysdep/rtl/gcc/gcc.cpp',
6680+ ]
6681+endif
6682+
6683+
6684+lowlevel_cpp_args = cpp_args
6685+lowlevel_cpp_args += '-DLIB_STATIC_LINK=1'
6686+lowlevel = static_library(
6687+ 'lowlevel',
6688+ lowlevel_sources,
6689+ include_directories: 'pch/lowlevel/',
6690+ dependencies: [dep_boost, dep_sdl2, dep_gl, dep_openal, dep_zlib, dep_png],
6691+ cpp_args: lowlevel_cpp_args,
6692+)
6693+
6694+link_with += lowlevel
6695+
6696+
6697+graphics_sources = [
6698+ 'graphics/CameraController.cpp',
6699+ 'graphics/Camera.cpp',
6700+ 'graphics/CinemaManager.cpp',
6701+ 'graphics/ColladaManager.cpp',
6702+ 'graphics/Color.cpp',
6703+ 'graphics/Decal.cpp',
6704+ 'graphics/Font.cpp',
6705+ 'graphics/FontManager.cpp',
6706+ 'graphics/FontMetrics.cpp',
6707+ 'graphics/Frustum.cpp',
6708+ 'graphics/GameView.cpp',
6709+ 'graphics/HeightMipmap.cpp',
6710+ 'graphics/HFTracer.cpp',
6711+ 'graphics/LightEnv.cpp',
6712+ 'graphics/LOSTexture.cpp',
6713+ 'graphics/MapGenerator.cpp',
6714+ 'graphics/MapIO.cpp',
6715+ 'graphics/MapReader.cpp',
6716+ 'graphics/MapWriter.cpp',
6717+ 'graphics/Material.cpp',
6718+ 'graphics/MaterialManager.cpp',
6719+ 'graphics/MeshManager.cpp',
6720+ 'graphics/MiniPatch.cpp',
6721+ 'graphics/ModelAbstract.cpp',
6722+ 'graphics/Model.cpp',
6723+ 'graphics/ModelDef.cpp',
6724+ 'graphics/ObjectBase.cpp',
6725+ 'graphics/ObjectEntry.cpp',
6726+ 'graphics/ObjectManager.cpp',
6727+ 'graphics/Overlay.cpp',
6728+ 'graphics/ParticleEmitter.cpp',
6729+ 'graphics/ParticleEmitterType.cpp',
6730+ 'graphics/ParticleManager.cpp',
6731+ 'graphics/Patch.cpp',
6732+ 'graphics/PreprocessorWrapper.cpp',
6733+ 'graphics/scripting/JSInterface_GameView.cpp',
6734+ 'graphics/ShaderDefines.cpp',
6735+ 'graphics/ShaderManager.cpp',
6736+ 'graphics/ShaderProgram.cpp',
6737+ 'graphics/ShaderProgramFFP.cpp',
6738+ 'graphics/ShaderTechnique.cpp',
6739+ 'graphics/SkeletonAnimDef.cpp',
6740+ 'graphics/SkeletonAnimManager.cpp',
6741+ 'graphics/SmoothedValue.cpp',
6742+ 'graphics/Terrain.cpp',
6743+ 'graphics/TerrainProperties.cpp',
6744+ 'graphics/TerrainTextureEntry.cpp',
6745+ 'graphics/TerrainTextureManager.cpp',
6746+ 'graphics/TerritoryBoundary.cpp',
6747+ 'graphics/TerritoryTexture.cpp',
6748+ 'graphics/TextRenderer.cpp',
6749+ 'graphics/TextureConverter.cpp',
6750+ 'graphics/TextureManager.cpp',
6751+ 'graphics/UnitAnimation.cpp',
6752+ 'graphics/Unit.cpp',
6753+ 'graphics/UnitManager.cpp',
6754+
6755+ 'renderer/AlphaMapCalculator.cpp',
6756+ 'renderer/DecalRData.cpp',
6757+ 'renderer/HWLightingModelRenderer.cpp',
6758+ 'renderer/InstancingModelRenderer.cpp',
6759+ 'renderer/MikktspaceWrap.cpp',
6760+ 'renderer/ModelRenderer.cpp',
6761+ 'renderer/OverlayRenderer.cpp',
6762+ 'renderer/ParticleRenderer.cpp',
6763+ 'renderer/PatchRData.cpp',
6764+ 'renderer/PostprocManager.cpp',
6765+ 'renderer/Renderer.cpp',
6766+ 'renderer/RenderingOptions.cpp',
6767+ 'renderer/RenderModifiers.cpp',
6768+ 'renderer/Scene.cpp',
6769+ 'renderer/scripting/JSInterface_Renderer.cpp',
6770+ 'renderer/ShadowMap.cpp',
6771+ 'renderer/SilhouetteRenderer.cpp',
6772+ 'renderer/SkyManager.cpp',
6773+ 'renderer/TerrainOverlay.cpp',
6774+ 'renderer/TerrainRenderer.cpp',
6775+ 'renderer/TexturedLineRData.cpp',
6776+ 'renderer/TimeManager.cpp',
6777+ 'renderer/VertexArray.cpp',
6778+ 'renderer/VertexBuffer.cpp',
6779+ 'renderer/VertexBufferManager.cpp',
6780+ 'renderer/WaterManager.cpp',
6781+
6782+ 'third_party/mikktspace/mikktspace.cpp',
6783+ 'third_party/mikktspace/weldmesh.cpp',
6784+ 'third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp',
6785+]
6786+graphics_cpp_args = cpp_args
6787+graphics_cpp_args += '-DLIB_STATIC_LINK=1 -DNVTT_SHARED=1'
6788+graphics = static_library(
6789+ 'graphics',
6790+ graphics_sources,
6791+ include_directories: 'pch/graphics/',
6792+ dependencies: [dep_boost, dep_gl, dep_js, dep_sdl2, dep_nvtt],
6793+ cpp_args: graphics_cpp_args,
6794+)
6795+link_with += graphics
6796+
6797+gui_sources = [
6798+ 'gui/CGUI.cpp',
6799+ 'gui/CGUIScrollBarVertical.cpp',
6800+ 'gui/CGUISetting.cpp',
6801+ 'gui/CGUISprite.cpp',
6802+ 'gui/CGUIText.cpp',
6803+ 'gui/GUIManager.cpp',
6804+ 'gui/GUIMatrix.cpp',
6805+ 'gui/GUIRenderer.cpp',
6806+ 'gui/GUIStringConversions.cpp',
6807+ 'gui/GUITooltip.cpp',
6808+ 'gui/IGUIScrollBar.cpp',
6809+ 'gui/ObjectBases/IGUIButtonBehavior.cpp',
6810+ 'gui/ObjectBases/IGUIObject.cpp',
6811+ 'gui/ObjectBases/IGUIScrollBarOwner.cpp',
6812+ 'gui/ObjectBases/IGUITextOwner.cpp',
6813+ 'gui/ObjectTypes/CButton.cpp',
6814+ 'gui/ObjectTypes/CChart.cpp',
6815+ 'gui/ObjectTypes/CCheckBox.cpp',
6816+ 'gui/ObjectTypes/CDropDown.cpp',
6817+ 'gui/ObjectTypes/CImage.cpp',
6818+ 'gui/ObjectTypes/CInput.cpp',
6819+ 'gui/ObjectTypes/CList.cpp',
6820+ 'gui/ObjectTypes/CMiniMap.cpp',
6821+ 'gui/ObjectTypes/COList.cpp',
6822+ 'gui/ObjectTypes/CProgressBar.cpp',
6823+ 'gui/ObjectTypes/CRadioButton.cpp',
6824+ 'gui/ObjectTypes/CSlider.cpp',
6825+ 'gui/ObjectTypes/CText.cpp',
6826+ 'gui/ObjectTypes/CTooltip.cpp',
6827+ 'gui/Scripting/GuiScriptConversions.cpp',
6828+ 'gui/Scripting/JSInterface_GUIManager.cpp',
6829+ 'gui/Scripting/JSInterface_GUISize.cpp',
6830+ 'gui/Scripting/JSInterface_IGUIObject.cpp',
6831+ 'gui/Scripting/ScriptFunctions.cpp',
6832+ 'gui/SettingTypes/CGUIColor.cpp',
6833+ 'gui/SettingTypes/CGUISize.cpp',
6834+ 'gui/SettingTypes/CGUIString.cpp',
6835+
6836+ 'i18n/L10n.cpp',
6837+ 'i18n/scripting/JSInterface_L10n.cpp',
6838+]
6839+gui_cpp_args = cpp_args
6840+gui_cpp_args += '-DLIB_STATIC_LINK=1 -DNVTT_SHARED=1'
6841+
6842+gui = static_library(
6843+ 'gui',
6844+ graphics_sources,
6845+ include_directories: 'pch/gui/',
6846+ dependencies: [dep_boost, dep_iconv, dep_js, dep_gl, dep_sdl2, dep_nvtt],
6847+ cpp_args: gui_cpp_args,
6848+)
6849+link_with += gui
6850+
6851+simulation_cpp_args = cpp_args
6852+simulation_sources = [
6853+ 'simulation2/components/CCmpAIManager.cpp',
6854+ 'simulation2/components/CCmpCinemaManager.cpp',
6855+ 'simulation2/components/CCmpCommandQueue.cpp',
6856+ 'simulation2/components/CCmpDecay.cpp',
6857+ 'simulation2/components/CCmpFootprint.cpp',
6858+ 'simulation2/components/CCmpMinimap.cpp',
6859+ 'simulation2/components/CCmpMotionBall.cpp',
6860+ 'simulation2/components/CCmpObstruction.cpp',
6861+ 'simulation2/components/CCmpObstructionManager.cpp',
6862+ 'simulation2/components/CCmpOverlayRenderer.cpp',
6863+ 'simulation2/components/CCmpOwnership.cpp',
6864+ 'simulation2/components/CCmpParticleManager.cpp',
6865+ 'simulation2/components/CCmpPathfinder.cpp',
6866+ 'simulation2/components/CCmpPosition.cpp',
6867+ 'simulation2/components/CCmpProjectileManager.cpp',
6868+ 'simulation2/components/CCmpRallyPointRenderer.cpp',
6869+ 'simulation2/components/CCmpRangeManager.cpp',
6870+ 'simulation2/components/CCmpRangeOverlayRenderer.cpp',
6871+ 'simulation2/components/CCmpSelectable.cpp',
6872+ 'simulation2/components/CCmpSoundManager.cpp',
6873+ 'simulation2/components/CCmpTemplateManager.cpp',
6874+ 'simulation2/components/CCmpTerrain.cpp',
6875+ 'simulation2/components/CCmpTerritoryInfluence.cpp',
6876+ 'simulation2/components/CCmpTerritoryManager.cpp',
6877+ 'simulation2/components/CCmpTest.cpp',
6878+ 'simulation2/components/CCmpUnitMotion.cpp',
6879+ 'simulation2/components/CCmpUnitRenderer.cpp',
6880+ 'simulation2/components/CCmpVision.cpp',
6881+ 'simulation2/components/CCmpVisualActor.cpp',
6882+ 'simulation2/components/CCmpWaterManager.cpp',
6883+ 'simulation2/components/ICmpAIInterface.cpp',
6884+ 'simulation2/components/ICmpAIManager.cpp',
6885+ 'simulation2/components/ICmpCinemaManager.cpp',
6886+ 'simulation2/components/ICmpCommandQueue.cpp',
6887+ 'simulation2/components/ICmpDecay.cpp',
6888+ 'simulation2/components/ICmpFogging.cpp',
6889+ 'simulation2/components/ICmpFootprint.cpp',
6890+ 'simulation2/components/ICmpGuiInterface.cpp',
6891+ 'simulation2/components/ICmpIdentity.cpp',
6892+ 'simulation2/components/ICmpMinimap.cpp',
6893+ 'simulation2/components/ICmpMirage.cpp',
6894+ 'simulation2/components/ICmpMotion.cpp',
6895+ 'simulation2/components/ICmpObstruction.cpp',
6896+ 'simulation2/components/ICmpObstructionManager.cpp',
6897+ 'simulation2/components/ICmpOverlayRenderer.cpp',
6898+ 'simulation2/components/ICmpOwnership.cpp',
6899+ 'simulation2/components/ICmpParticleManager.cpp',
6900+ 'simulation2/components/ICmpPathfinder.cpp',
6901+ 'simulation2/components/ICmpPlayer.cpp',
6902+ 'simulation2/components/ICmpPlayerManager.cpp',
6903+ 'simulation2/components/ICmpPosition.cpp',
6904+ 'simulation2/components/ICmpProjectileManager.cpp',
6905+ 'simulation2/components/ICmpRallyPoint.cpp',
6906+ 'simulation2/components/ICmpRallyPointRenderer.cpp',
6907+ 'simulation2/components/ICmpRangeManager.cpp',
6908+ 'simulation2/components/ICmpRangeOverlayRenderer.cpp',
6909+ 'simulation2/components/ICmpSelectable.cpp',
6910+ 'simulation2/components/ICmpSettlement.cpp',
6911+ 'simulation2/components/ICmpSound.cpp',
6912+ 'simulation2/components/ICmpSoundManager.cpp',
6913+ 'simulation2/components/ICmpTemplateManager.cpp',
6914+ 'simulation2/components/ICmpTerrain.cpp',
6915+ 'simulation2/components/ICmpTerritoryDecayManager.cpp',
6916+ 'simulation2/components/ICmpTerritoryInfluence.cpp',
6917+ 'simulation2/components/ICmpTerritoryManager.cpp',
6918+ 'simulation2/components/ICmpTest.cpp',
6919+ 'simulation2/components/ICmpUnitMotion.cpp',
6920+ 'simulation2/components/ICmpUnitRenderer.cpp',
6921+ 'simulation2/components/ICmpUnknownScript.cpp',
6922+ 'simulation2/components/ICmpValueModificationManager.cpp',
6923+ 'simulation2/components/ICmpVisibility.cpp',
6924+ 'simulation2/components/ICmpVision.cpp',
6925+ 'simulation2/components/ICmpVisual.cpp',
6926+ 'simulation2/components/ICmpWaterManager.cpp',
6927+ 'simulation2/helpers/CinemaPath.cpp',
6928+ 'simulation2/helpers/Geometry.cpp',
6929+ 'simulation2/helpers/HierarchicalPathfinder.cpp',
6930+ 'simulation2/helpers/LongPathfinder.cpp',
6931+ 'simulation2/helpers/PathGoal.cpp',
6932+ 'simulation2/helpers/Rasterize.cpp',
6933+ 'simulation2/helpers/Render.cpp',
6934+ 'simulation2/helpers/Selection.cpp',
6935+ 'simulation2/helpers/VertexPathfinder.cpp',
6936+ 'simulation2/scripting/EngineScriptConversions.cpp',
6937+ 'simulation2/scripting/JSInterface_Simulation.cpp',
6938+ 'simulation2/scripting/MessageTypeConversions.cpp',
6939+ 'simulation2/scripting/ScriptComponent.cpp',
6940+ 'simulation2/serialization/BinarySerializer.cpp',
6941+ 'simulation2/serialization/DebugSerializer.cpp',
6942+ 'simulation2/serialization/HashSerializer.cpp',
6943+ 'simulation2/serialization/IDeserializer.cpp',
6944+ 'simulation2/serialization/ISerializer.cpp',
6945+ 'simulation2/serialization/StdDeserializer.cpp',
6946+ 'simulation2/serialization/StdSerializer.cpp',
6947+ 'simulation2/Simulation2.cpp',
6948+ 'simulation2/system/CmpPtr.cpp',
6949+ 'simulation2/system/ComponentManager.cpp',
6950+ 'simulation2/system/ComponentManagerSerialization.cpp',
6951+ 'simulation2/system/DynamicSubscription.cpp',
6952+ 'simulation2/system/IComponent.cpp',
6953+ 'simulation2/system/LocalTurnManager.cpp',
6954+ 'simulation2/system/ParamNode.cpp',
6955+ 'simulation2/system/ReplayTurnManager.cpp',
6956+ 'simulation2/system/SimContext.cpp',
6957+ 'simulation2/system/TurnManager.cpp',
6958+]
6959+
6960+simulation = static_library(
6961+ 'simulation',
6962+ simulation_sources,
6963+ include_directories: 'pch/simulation2/',
6964+ dependencies: [dep_boost, dep_js, dep_gl],
6965+ cpp_args: simulation_cpp_args,
6966+)
6967+
6968+link_with += simulation
6969+
6970+engine_cpp_args = cpp_args
6971+engine_sources = [
6972+ 'third_party/cppformat/format.cpp',
6973+
6974+ 'i18n/L10n.cpp',
6975+ 'i18n/scripting/JSInterface_L10n.cpp',
6976+ 'maths/BoundingBoxAligned.cpp',
6977+ 'maths/BoundingBoxOriented.cpp',
6978+ 'maths/BoundingSphere.cpp',
6979+ 'maths/Brush.cpp',
6980+ 'maths/Fixed.cpp',
6981+ 'maths/Matrix3D.cpp',
6982+ 'maths/MD5.cpp',
6983+ 'maths/Noise.cpp',
6984+ 'maths/NUSpline.cpp',
6985+ 'maths/Plane.cpp',
6986+ 'maths/Quaternion.cpp',
6987+ 'maths/Sqrt.cpp',
6988+ 'maths/Vector3D.cpp',
6989+ 'ps/ArchiveBuilder.cpp',
6990+ 'ps/CacheLoader.cpp',
6991+ 'ps/CConsole.cpp',
6992+ 'ps/CLogger.cpp',
6993+ 'ps/Compress.cpp',
6994+ 'ps/ConfigDB.cpp',
6995+ 'ps/CStr.cpp',
6996+ 'ps/CStrIntern.cpp',
6997+ 'ps/DllLoader.cpp',
6998+ 'ps/Errors.cpp',
6999+ 'ps/FileIo.cpp',
7000+ 'ps/Filesystem.cpp',
7001+ 'ps/Game.cpp',
7002+ 'ps/GameSetup/Atlas.cpp',
7003+ 'ps/GameSetup/CmdLineArgs.cpp',
7004+ 'ps/GameSetup/Config.cpp',
7005+ 'ps/GameSetup/GameSetup.cpp',
7006+ 'ps/GameSetup/HWDetect.cpp',
7007+ 'ps/GameSetup/Paths.cpp',
7008+ 'ps/Globals.cpp',
7009+ 'ps/GUID.cpp',
7010+ 'ps/Hotkey.cpp',
7011+ 'ps/Joystick.cpp',
7012+ 'ps/KeyName.cpp',
7013+ 'ps/Loader.cpp',
7014+ 'ps/Mod.cpp',
7015+ 'ps/ModInstaller.cpp',
7016+ 'ps/ModIo.cpp',
7017+ 'ps/Profile.cpp',
7018+ 'ps/Profiler2.cpp',
7019+ 'ps/Profiler2GPU.cpp',
7020+ 'ps/ProfileViewer.cpp',
7021+ 'ps/Pyrogenesis.cpp',
7022+ 'ps/Replay.cpp',
7023+ 'ps/SavedGame.cpp',
7024+ 'ps/scripting/JSInterface_ConfigDB.cpp',
7025+ 'ps/scripting/JSInterface_Console.cpp',
7026+ 'ps/scripting/JSInterface_Debug.cpp',
7027+ 'ps/scripting/JSInterface_Game.cpp',
7028+ 'ps/scripting/JSInterface_Main.cpp',
7029+ 'ps/scripting/JSInterface_Mod.cpp',
7030+ 'ps/scripting/JSInterface_ModIo.cpp',
7031+ 'ps/scripting/JSInterface_SavedGame.cpp',
7032+ 'ps/scripting/JSInterface_UserReport.cpp',
7033+ 'ps/scripting/JSInterface_VFS.cpp',
7034+ 'ps/scripting/JSInterface_VisualReplay.cpp',
7035+ 'ps/Shapes.cpp',
7036+ 'ps/TemplateLoader.cpp',
7037+ 'ps/ThreadUtil.cpp',
7038+ 'ps/TouchInput.cpp',
7039+ 'ps/UserReport.cpp',
7040+ 'ps/Util.cpp',
7041+ 'ps/VideoMode.cpp',
7042+ 'ps/VisualReplay.cpp',
7043+ 'ps/World.cpp',
7044+ 'ps/XML/RelaxNG.cpp',
7045+ 'ps/XML/Xeromyces.cpp',
7046+ 'ps/XML/XeroXMB.cpp',
7047+ 'ps/XML/XMLWriter.cpp',
7048+]
7049+
7050+engine = static_library(
7051+ 'engine',
7052+ engine_sources,
7053+ include_directories: 'pch/engine/',
7054+ 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],
7055+ cpp_args: simulation_cpp_args,
7056+)
7057+link_with+= engine
7058+
7059+
7060+mongoose = static_library(
7061+ 'mongoose',
7062+ ['third_party/mongoose/mongoose.cpp'],
7063+ cpp_args: simulation_cpp_args,
7064+)
7065+link_with+= mongoose
7066+
7067+
7068+
7069+scriptinterface_sources = [
7070+ 'scriptinterface/ScriptConversions.cpp',
7071+ 'scriptinterface/ScriptInterface.cpp',
7072+ 'scriptinterface/ScriptRuntime.cpp',
7073+ 'scriptinterface/ScriptStats.cpp',
7074+]
7075+scriptinterface = static_library(
7076+ 'scriptinterface',
7077+ scriptinterface_sources,
7078+ include_directories: [
7079+ 'pch/scriptinterface/',
7080+ 'third_party/',
7081+ ],
7082+
7083+ dependencies: [dep_boost, dep_js,dep_valgrind],
7084+ cpp_args: cpp_args,
7085+)
7086+link_with+= scriptinterface
7087+
7088+if false
7089+ sources += [
7090+ 'test_setup.cpp',
7091+ ]
7092+endif
7093+
7094+mocks_real = static_library(
7095+ 'mocks_real',
7096+ ['mocks/mocks_real.cpp'],
7097+ dependencies: [dep_boost],
7098+ cpp_args: cpp_args,
7099+)
7100+
7101+mocks_test = static_library(
7102+ 'mocks_test',
7103+ ['mocks/mocks_test.cpp'],
7104+ dependencies: [dep_boost],
7105+ cpp_args: cpp_args,
7106+)
7107+
7108+if get_option('atlas')
7109+ atlasobject_cpp_args = cpp_args
7110+ atlasobject_sources = [
7111+ 'tools/atlas/AtlasObject/AtlasObjectImpl.cpp',
7112+ 'tools/atlas/AtlasObject/AtlasObjectJS.cpp',
7113+ 'tools/atlas/AtlasObject/AtlasObjectText.cpp',
7114+ 'tools/atlas/AtlasObject/AtlasObjectXML.cpp',
7115+ ]
7116+ atlasobject = static_library(
7117+ 'AtlasObject',
7118+ atlasobject_sources,
7119+ include_directories: ['third_party/jsonspirit/', 'pch/atlas/'],
7120+ dependencies: [dep_boost],
7121+ cpp_args: simulation_cpp_args,
7122+ )
7123+ atlasui_cpp_args = cpp_args
7124+ atlasui_sources = [
7125+ 'tools/atlas/AtlasUI/ActorEditor/ActorEditor.cpp',
7126+ 'tools/atlas/AtlasUI/ActorEditor/ActorEditorListCtrl.cpp',
7127+ 'tools/atlas/AtlasUI/ActorEditor/AnimListEditor.cpp',
7128+ 'tools/atlas/AtlasUI/ActorEditor/PropListEditor.cpp',
7129+ 'tools/atlas/AtlasUI/ActorEditor/TexListEditor.cpp',
7130+ 'tools/atlas/AtlasUI/CustomControls/Buttons/ActionButton.cpp',
7131+ 'tools/atlas/AtlasUI/CustomControls/Buttons/ToolButton.cpp',
7132+ 'tools/atlas/AtlasUI/CustomControls/Canvas/Canvas.cpp',
7133+ 'tools/atlas/AtlasUI/CustomControls/ColorDialog/ColorDialog.cpp',
7134+ 'tools/atlas/AtlasUI/CustomControls/DraggableListCtrl/DraggableListCtrlCommands.cpp',
7135+ 'tools/atlas/AtlasUI/CustomControls/DraggableListCtrl/DraggableListCtrl.cpp',
7136+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/EditableListCtrlCommands.cpp',
7137+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/EditableListCtrl.cpp',
7138+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/FieldEditCtrl.cpp',
7139+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/ListCtrlValidator.cpp',
7140+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/QuickComboBox.cpp',
7141+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/QuickFileCtrl.cpp',
7142+ 'tools/atlas/AtlasUI/CustomControls/EditableListCtrl/QuickTextCtrl.cpp',
7143+ 'tools/atlas/AtlasUI/CustomControls/FileHistory/FileHistory.cpp',
7144+ 'tools/atlas/AtlasUI/CustomControls/HighResTimer/HighResTimer.cpp',
7145+ 'tools/atlas/AtlasUI/CustomControls/MapDialog/MapDialog.cpp',
7146+ 'tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.cpp',
7147+ 'tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/virtualdirtreectrl.cpp',
7148+ 'tools/atlas/AtlasUI/CustomControls/Windows/AtlasDialog.cpp',
7149+ 'tools/atlas/AtlasUI/CustomControls/Windows/AtlasWindow.cpp',
7150+ 'tools/atlas/AtlasUI/General/AtlasClipboard.cpp',
7151+ 'tools/atlas/AtlasUI/General/AtlasEventLoop.cpp',
7152+ 'tools/atlas/AtlasUI/General/AtlasWindowCommand.cpp',
7153+ 'tools/atlas/AtlasUI/General/AtlasWindowCommandProc.cpp',
7154+ 'tools/atlas/AtlasUI/General/Datafile.cpp',
7155+ 'tools/atlas/AtlasUI/General/Observable.cpp',
7156+ 'tools/atlas/AtlasUI/Misc/DLLInterface.cpp',
7157+ 'tools/atlas/AtlasUI/Misc/KeyMap.cpp',
7158+ 'tools/atlas/AtlasUI/Misc/precompiled.cpp',
7159+ 'tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp',
7160+ 'tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp',
7161+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Cinema/Cinema.cpp',
7162+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.cpp',
7163+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp',
7164+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/LightControl.cpp',
7165+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp',
7166+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp',
7167+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/VariationControl.cpp',
7168+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp',
7169+ 'tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp',
7170+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/ActorViewerTool.cpp',
7171+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/AlterElevation.cpp',
7172+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp',
7173+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/MiscState.cpp',
7174+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp',
7175+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.cpp',
7176+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/FillTerrain.cpp',
7177+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/FlattenElevation.cpp',
7178+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/PaintTerrain.cpp',
7179+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/PickWaterHeight.cpp',
7180+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/PikeElevation.cpp',
7181+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/PlaceObject.cpp',
7182+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/ReplaceTerrain.cpp',
7183+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/SmoothElevation.cpp',
7184+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/TransformObject.cpp',
7185+ 'tools/atlas/AtlasUI/ScenarioEditor/Tools/TransformPath.cpp',
7186+ ]
7187+
7188+ atlasui = library(
7189+ 'AtlasUI', # Called in Atlas.cpp
7190+ atlasui_sources,
7191+ include_directories: [
7192+ 'pch/atlas/',
7193+ 'tools/atlas/AtlasUI/CustomControls/',
7194+ ],
7195+ dependencies: [dep_iconv, dep_libxml2, dep_boost, dep_js],
7196+ link_with: [atlasobject],
7197+ cpp_args: atlasui_cpp_args,
7198+ )
7199+
7200+ actoreditor_sources = [
7201+ 'tools/atlas/AtlasFrontends/ActorEditor.cpp',
7202+ ]
7203+ executable(
7204+ 'actoreditor',
7205+ actoreditor_sources,
7206+ dependencies: dependencies,
7207+ link_with: [atlasobject, atlasui],
7208+ cpp_args: cpp_args,
7209+ install: true
7210+ )
7211+endif
7212+
7213+
7214+
7215+atlas_sources = [
7216+
7217+# 'tools/atlas/AtlasFrontends/_template.cpp',
7218+ 'tools/atlas/GameInterface/ActorViewer.cpp',
7219+ 'tools/atlas/GameInterface/Brushes.cpp',
7220+ 'tools/atlas/GameInterface/CommandProc.cpp',
7221+ 'tools/atlas/GameInterface/GameLoop.cpp',
7222+ 'tools/atlas/GameInterface/InputProcessor.cpp',
7223+ 'tools/atlas/GameInterface/MessagePasserImpl.cpp',
7224+ 'tools/atlas/GameInterface/Misc.cpp',
7225+ 'tools/atlas/GameInterface/Register.cpp',
7226+ 'tools/atlas/GameInterface/SimState.cpp',
7227+ 'tools/atlas/GameInterface/View.cpp',
7228+ 'tools/atlas/GameInterface/Handlers/BrushHandlers.cpp',
7229+ 'tools/atlas/GameInterface/Handlers/CameraCtrlHandlers.cpp',
7230+ 'tools/atlas/GameInterface/Handlers/CinemaHandler.cpp',
7231+ 'tools/atlas/GameInterface/Handlers/CommandHandlers.cpp',
7232+ 'tools/atlas/GameInterface/Handlers/ElevationHandlers.cpp',
7233+ 'tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp',
7234+ 'tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp',
7235+ 'tools/atlas/GameInterface/Handlers/MapHandlers.cpp',
7236+ 'tools/atlas/GameInterface/Handlers/MessageHandler.cpp',
7237+ 'tools/atlas/GameInterface/Handlers/MiscHandlers.cpp',
7238+ 'tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp',
7239+ 'tools/atlas/GameInterface/Handlers/PlayerHandlers.cpp',
7240+ 'tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp',
7241+]
7242+
7243+atlas = static_library(
7244+ 'atlas',
7245+ atlas_sources,
7246+ include_directories: [
7247+ 'pch/atlas/',
7248+ ],
7249+ dependencies: [dep_boost, dep_js, dep_gl, dep_sdl2],
7250+ cpp_args: cpp_args,
7251+)
7252+
7253+link_with += atlas
7254+
7255+
7256+pyrogenesis_cpp_args = cpp_args
7257+sources = [
7258+ 'main.cpp',
7259+
7260+]
7261+if host_machine.system() == 'windows'
7262+windows = import('windows')
7263+ pyrogenesis_cpp_args += '-DHAVE_ICONV_CONST=1 -Dtinygettext_ICONV_CONST=const -DICONV_CONST=const -DNVTT_SHARED=1'
7264+ sources += [
7265+ windows.compile_resources('lib/sysdep/os/win/error_dialog.rc'),
7266+ windows.compile_resources('lib/sysdep/os/win/icon.rc'),
7267+ ]
7268+endif
7269+
7270+
7271+executable(
7272+ 'pyrogenesis',
7273+ sources,
7274+ include_directories: 'lib',
7275+ dependencies: dependencies,
7276+ link_with: link_with,
7277+ cpp_args: pyrogenesis_cpp_args,
7278+ install: true
7279+)
7280+
7281+
7282+# pyrogenesis_test_sources = [
7283+# 'graphics/tests/test_Camera.cpp',
7284+# # 'gui/tests/test_*',
7285+# # 'lib/tests/test_*',
7286+# # 'maths/tests/test_*',
7287+# # 'network/tests/test_*',
7288+# # 'ps/tests/test_*',
7289+# # 'scriptinterface/tests/test_*',
7290+# # 'simulation2/tests/test_*',
7291+# # 'simulation2/components/tests/test_*',
7292+# # 'third_party/encryption/tests/test_*',
7293+# # 'third_party/ogre3d_preprocessor/tests/test_*',
7294+# # 'tools/atlas/tests/test_*',
7295+# # 'ps/GameSetup/tests/test_*',
7296+# # 'ps/XML/tests/test_*',
7297+# ]
7298+
7299+
7300+# executable(
7301+# 'pyrogenesis-test',
7302+# pyrogenesis_test_sources,
7303+# include_directories: [
7304+# 'lib',
7305+# 'pch/test/',
7306+# ],
7307+# dependencies: dependencies,
7308+# link_with: [atlas, engine, glooxwrapper, graphics, gui, lobby, lowlevel, mocks_test, mongoose, network, scriptinterface, simulation, tinygettext],
7309+# cpp_args: pyrogenesis_cpp_args,
7310+# install: true
7311+# )
7312Index: source/ps/CConsole.cpp
7313===================================================================
7314--- source/ps/CConsole.cpp (revision 23275)
7315+++ source/ps/CConsole.cpp (working copy)
7316@@ -1,699 +1,700 @@
7317 /* Copyright (C) 2019 Wildfire Games.
7318 * This file is part of 0 A.D.
7319 *
7320 * 0 A.D. is free software: you can redistribute it and/or modify
7321 * it under the terms of the GNU General Public License as published by
7322 * the Free Software Foundation, either version 2 of the License, or
7323 * (at your option) any later version.
7324 *
7325 * 0 A.D. is distributed in the hope that it will be useful,
7326 * but WITHOUT ANY WARRANTY; without even the implied warranty of
7327 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7328 * GNU General Public License for more details.
7329 *
7330 * You should have received a copy of the GNU General Public License
7331 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
7332 */
7333
7334 /*
7335 * Implements the in-game console with scripting support.
7336 */
7337
7338 #include "precompiled.h"
7339 #include <wctype.h>
7340
7341 #include "CConsole.h"
7342
7343 #include "graphics/FontMetrics.h"
7344 #include "graphics/ShaderManager.h"
7345 #include "graphics/TextRenderer.h"
7346 #include "gui/CGUI.h"
7347 #include "gui/GUIManager.h"
7348 #include "gui/GUIMatrix.h"
7349 #include "lib/ogl.h"
7350-#include "lib/sysdep/clipboard.h"
7351 #include "lib/timer.h"
7352 #include "lib/utf8.h"
7353 #include "maths/MathUtil.h"
7354 #include "network/NetClient.h"
7355 #include "network/NetServer.h"
7356 #include "ps/CLogger.h"
7357 #include "ps/Filesystem.h"
7358 #include "ps/GameSetup/Config.h"
7359 #include "ps/Globals.h"
7360 #include "ps/Hotkey.h"
7361 #include "ps/Profile.h"
7362 #include "ps/Pyrogenesis.h"
7363 #include "renderer/Renderer.h"
7364 #include "scriptinterface/ScriptInterface.h"
7365
7366 CConsole* g_Console = 0;
7367
7368 CConsole::CConsole()
7369 {
7370 m_bToggle = false;
7371 m_bVisible = false;
7372
7373 m_fVisibleFrac = 0.0f;
7374
7375 m_szBuffer = new wchar_t[CONSOLE_BUFFER_SIZE];
7376 FlushBuffer();
7377
7378 m_iMsgHistPos = 1;
7379 m_charsPerPage = 0;
7380
7381 m_prevTime = 0.0;
7382 m_bCursorVisState = true;
7383 m_cursorBlinkRate = 0.5;
7384
7385 InsertMessage("[ 0 A.D. Console v0.14 ]");
7386 InsertMessage("");
7387 }
7388
7389 CConsole::~CConsole()
7390 {
7391 delete[] m_szBuffer;
7392 }
7393
7394
7395 void CConsole::SetSize(float X, float Y, float W, float H)
7396 {
7397 m_fX = X;
7398 m_fY = Y;
7399 m_fWidth = W;
7400 m_fHeight = H;
7401 }
7402
7403 void CConsole::UpdateScreenSize(int w, int h)
7404 {
7405 float height = h * 0.6f;
7406 SetSize(0, 0, w / g_GuiScale, height / g_GuiScale);
7407 }
7408
7409
7410 void CConsole::ToggleVisible()
7411 {
7412 m_bToggle = true;
7413 m_bVisible = !m_bVisible;
7414
7415 // TODO: this should be based on input focus, not visibility
7416 if (m_bVisible)
7417 SDL_StartTextInput();
7418 else
7419 SDL_StopTextInput();
7420 }
7421
7422 void CConsole::SetVisible(bool visible)
7423 {
7424 if (visible != m_bVisible)
7425 m_bToggle = true;
7426 m_bVisible = visible;
7427 if (visible)
7428 {
7429 m_prevTime = 0.0;
7430 m_bCursorVisState = false;
7431 }
7432 }
7433
7434 void CConsole::SetCursorBlinkRate(double rate)
7435 {
7436 m_cursorBlinkRate = rate;
7437 }
7438
7439 void CConsole::FlushBuffer()
7440 {
7441 // Clear the buffer and set the cursor and length to 0
7442 memset(m_szBuffer, '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE);
7443 m_iBufferPos = m_iBufferLength = 0;
7444 }
7445
7446
7447 void CConsole::Update(const float deltaRealTime)
7448 {
7449 if(m_bToggle)
7450 {
7451 const float AnimateTime = .30f;
7452 const float Delta = deltaRealTime / AnimateTime;
7453 if(m_bVisible)
7454 {
7455 m_fVisibleFrac += Delta;
7456 if(m_fVisibleFrac > 1.0f)
7457 {
7458 m_fVisibleFrac = 1.0f;
7459 m_bToggle = false;
7460 }
7461 }
7462 else
7463 {
7464 m_fVisibleFrac -= Delta;
7465 if(m_fVisibleFrac < 0.0f)
7466 {
7467 m_fVisibleFrac = 0.0f;
7468 m_bToggle = false;
7469 }
7470 }
7471 }
7472 }
7473
7474 //Render Manager.
7475 void CConsole::Render()
7476 {
7477 if (! (m_bVisible || m_bToggle) ) return;
7478
7479 PROFILE3_GPU("console");
7480
7481 glEnable(GL_BLEND);
7482 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
7483
7484 CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
7485 solidTech->BeginPass();
7486 CShaderProgramPtr solidShader = solidTech->GetShader();
7487
7488 CMatrix3D transform = GetDefaultGuiMatrix();
7489
7490 // animation: slide in from top of screen
7491 const float DeltaY = (1.0f - m_fVisibleFrac) * m_fHeight;
7492 transform.PostTranslate(m_fX, m_fY - DeltaY, 0.0f); // move to window position
7493 solidShader->Uniform(str_transform, transform);
7494
7495 DrawWindow(solidShader);
7496
7497 solidTech->EndPass();
7498
7499 CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
7500 textTech->BeginPass();
7501 CTextRenderer textRenderer(textTech->GetShader());
7502 textRenderer.Font(CStrIntern(CONSOLE_FONT));
7503 textRenderer.SetTransform(transform);
7504
7505 DrawHistory(textRenderer);
7506 DrawBuffer(textRenderer);
7507
7508 textRenderer.Render();
7509
7510 textTech->EndPass();
7511
7512 glDisable(GL_BLEND);
7513 }
7514
7515
7516 void CConsole::DrawWindow(CShaderProgramPtr& shader)
7517 {
7518 float boxVerts[] = {
7519 m_fWidth, 0.0f,
7520 1.0f, 0.0f,
7521 1.0f, m_fHeight-1.0f,
7522 m_fWidth, m_fHeight-1.0f
7523 };
7524
7525 shader->VertexPointer(2, GL_FLOAT, 0, boxVerts);
7526
7527 // Draw Background
7528 // Set the color to a translucent blue
7529 shader->Uniform(str_color, 0.0f, 0.0f, 0.5f, 0.6f);
7530 shader->AssertPointersBound();
7531 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
7532
7533 // Draw Border
7534 // Set the color to a translucent yellow
7535 shader->Uniform(str_color, 0.5f, 0.5f, 0.0f, 0.6f);
7536 shader->AssertPointersBound();
7537 glDrawArrays(GL_LINE_LOOP, 0, 4);
7538
7539 if (m_fHeight > m_iFontHeight + 4)
7540 {
7541 float lineVerts[] = {
7542 0.0f, m_fHeight - (float)m_iFontHeight - 4.0f,
7543 m_fWidth, m_fHeight - (float)m_iFontHeight - 4.0f
7544 };
7545 shader->VertexPointer(2, GL_FLOAT, 0, lineVerts);
7546 shader->AssertPointersBound();
7547 glDrawArrays(GL_LINES, 0, 2);
7548 }
7549 }
7550
7551
7552 void CConsole::DrawHistory(CTextRenderer& textRenderer)
7553 {
7554 int i = 1;
7555
7556 std::deque<std::wstring>::iterator Iter; //History iterator
7557
7558 std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
7559
7560 textRenderer.Color(1.0f, 1.0f, 1.0f);
7561
7562 for (Iter = m_deqMsgHistory.begin();
7563 Iter != m_deqMsgHistory.end()
7564 && (((i - m_iMsgHistPos + 1) * m_iFontHeight) < m_fHeight);
7565 ++Iter)
7566 {
7567 if (i >= m_iMsgHistPos)
7568 textRenderer.Put(9.0f, m_fHeight - (float)m_iFontOffset - (float)m_iFontHeight * (i - m_iMsgHistPos + 1), Iter->c_str());
7569
7570 i++;
7571 }
7572 }
7573
7574 // Renders the buffer to the screen.
7575 void CConsole::DrawBuffer(CTextRenderer& textRenderer)
7576 {
7577 if (m_fHeight < m_iFontHeight)
7578 return;
7579
7580 CMatrix3D savedTransform = textRenderer.GetTransform();
7581
7582 textRenderer.Translate(2.0f, m_fHeight - (float)m_iFontOffset + 1.0f, 0.0f);
7583
7584 textRenderer.Color(1.0f, 1.0f, 0.0f);
7585 textRenderer.PutAdvance(L"]");
7586
7587 textRenderer.Color(1.0f, 1.0f, 1.0f);
7588
7589 if (m_iBufferPos == 0)
7590 DrawCursor(textRenderer);
7591
7592 for (int i = 0; i < m_iBufferLength; i++)
7593 {
7594 textRenderer.PrintfAdvance(L"%lc", m_szBuffer[i]);
7595 if (m_iBufferPos-1 == i)
7596 DrawCursor(textRenderer);
7597 }
7598
7599 textRenderer.SetTransform(savedTransform);
7600 }
7601
7602 void CConsole::DrawCursor(CTextRenderer& textRenderer)
7603 {
7604 if (m_cursorBlinkRate > 0.0)
7605 {
7606 // check if the cursor visibility state needs to be changed
7607 double currTime = timer_Time();
7608 if ((currTime - m_prevTime) >= m_cursorBlinkRate)
7609 {
7610 m_bCursorVisState = !m_bCursorVisState;
7611 m_prevTime = currTime;
7612 }
7613 }
7614 else
7615 {
7616 // Should always be visible
7617 m_bCursorVisState = true;
7618 }
7619
7620 if(m_bCursorVisState)
7621 {
7622 // Slightly translucent yellow
7623 textRenderer.Color(1.0f, 1.0f, 0.0f, 0.8f);
7624
7625 // Cursor character is chosen to be an underscore
7626 textRenderer.Put(0.0f, 0.0f, L"_");
7627
7628 // Revert to the standard text color
7629 textRenderer.Color(1.0f, 1.0f, 1.0f);
7630 }
7631 }
7632
7633
7634 //Inserts a character into the buffer.
7635 void CConsole::InsertChar(const int szChar, const wchar_t cooked)
7636 {
7637 static int iHistoryPos = -1;
7638
7639 if (!m_bVisible) return;
7640
7641 switch (szChar)
7642 {
7643 case SDLK_RETURN:
7644 iHistoryPos = -1;
7645 m_iMsgHistPos = 1;
7646 ProcessBuffer(m_szBuffer);
7647 FlushBuffer();
7648 return;
7649
7650 case SDLK_TAB:
7651 // Auto Complete
7652 return;
7653
7654 case SDLK_BACKSPACE:
7655 if (IsEmpty() || IsBOB()) return;
7656
7657 if (m_iBufferPos == m_iBufferLength)
7658 m_szBuffer[m_iBufferPos - 1] = '\0';
7659 else
7660 {
7661 for (int j = m_iBufferPos-1; j < m_iBufferLength-1; j++)
7662 m_szBuffer[j] = m_szBuffer[j+1]; // move chars to left
7663 m_szBuffer[m_iBufferLength-1] = '\0';
7664 }
7665
7666 m_iBufferPos--;
7667 m_iBufferLength--;
7668 return;
7669
7670 case SDLK_DELETE:
7671 if (IsEmpty() || IsEOB()) return;
7672
7673 if (m_iBufferPos == m_iBufferLength-1)
7674 {
7675 m_szBuffer[m_iBufferPos] = '\0';
7676 m_iBufferLength--;
7677 }
7678 else
7679 {
7680 if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
7681 {
7682 // Make Ctrl-Delete delete up to end of line
7683 m_szBuffer[m_iBufferPos] = '\0';
7684 m_iBufferLength = m_iBufferPos;
7685 }
7686 else
7687 {
7688 // Delete just one char and move the others left
7689 for(int j=m_iBufferPos; j<m_iBufferLength-1; j++)
7690 m_szBuffer[j] = m_szBuffer[j+1];
7691 m_szBuffer[m_iBufferLength-1] = '\0';
7692 m_iBufferLength--;
7693 }
7694 }
7695
7696 return;
7697
7698 case SDLK_HOME:
7699 if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
7700 {
7701 std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
7702
7703 int linesShown = (int)m_fHeight/m_iFontHeight - 4;
7704 m_iMsgHistPos = Clamp(static_cast<int>(m_deqMsgHistory.size()) - linesShown, 1, static_cast<int>(m_deqMsgHistory.size()));
7705 }
7706 else
7707 {
7708 m_iBufferPos = 0;
7709 }
7710 return;
7711
7712 case SDLK_END:
7713 if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
7714 {
7715 m_iMsgHistPos = 1;
7716 }
7717 else
7718 {
7719 m_iBufferPos = m_iBufferLength;
7720 }
7721 return;
7722
7723 case SDLK_LEFT:
7724 if (m_iBufferPos) m_iBufferPos--;
7725 return;
7726
7727 case SDLK_RIGHT:
7728 if (m_iBufferPos != m_iBufferLength) m_iBufferPos++;
7729 return;
7730
7731 // BEGIN: Buffer History Lookup
7732 case SDLK_UP:
7733 if (m_deqBufHistory.size() && iHistoryPos != (int)m_deqBufHistory.size() - 1)
7734 {
7735 iHistoryPos++;
7736 SetBuffer(m_deqBufHistory.at(iHistoryPos).c_str());
7737 m_iBufferPos = m_iBufferLength;
7738 }
7739 return;
7740
7741 case SDLK_DOWN:
7742 if (m_deqBufHistory.size())
7743 {
7744 if (iHistoryPos > 0)
7745 {
7746 iHistoryPos--;
7747 SetBuffer(m_deqBufHistory.at(iHistoryPos).c_str());
7748 m_iBufferPos = m_iBufferLength;
7749 }
7750 else if (iHistoryPos == 0)
7751 {
7752 iHistoryPos--;
7753 FlushBuffer();
7754 }
7755 }
7756 return;
7757 // END: Buffer History Lookup
7758
7759 // BEGIN: Message History Lookup
7760 case SDLK_PAGEUP:
7761 {
7762 std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
7763
7764 if (m_iMsgHistPos != (int)m_deqMsgHistory.size()) m_iMsgHistPos++;
7765 return;
7766 }
7767
7768 case SDLK_PAGEDOWN:
7769 if (m_iMsgHistPos != 1) m_iMsgHistPos--;
7770 return;
7771 // END: Message History Lookup
7772
7773 default: //Insert a character
7774 if (IsFull()) return;
7775 if (cooked == 0) return;
7776
7777 if (IsEOB()) //are we at the end of the buffer?
7778 m_szBuffer[m_iBufferPos] = cooked; //cat char onto end
7779 else
7780 { //we need to insert
7781 int i;
7782 for(i=m_iBufferLength; i>m_iBufferPos; i--)
7783 m_szBuffer[i] = m_szBuffer[i-1]; // move chars to right
7784 m_szBuffer[i] = cooked;
7785 }
7786
7787 m_iBufferPos++;
7788 m_iBufferLength++;
7789
7790 return;
7791 }
7792 }
7793
7794
7795 void CConsole::InsertMessage(const std::string& message)
7796 {
7797 // (TODO: this text-wrapping is rubbish since we now use variable-width fonts)
7798
7799 //Insert newlines to wraparound text where needed
7800 std::wstring wrapAround = wstring_from_utf8(message.c_str());
7801 std::wstring newline(L"\n");
7802 size_t oldNewline=0;
7803 size_t distance;
7804
7805 //make sure everything has been initialized
7806 if ( m_charsPerPage != 0 )
7807 {
7808 while ( oldNewline+m_charsPerPage < wrapAround.length() )
7809 {
7810 distance = wrapAround.find(newline, oldNewline) - oldNewline;
7811 if ( distance > m_charsPerPage )
7812 {
7813 oldNewline += m_charsPerPage;
7814 wrapAround.insert( oldNewline++, newline );
7815 }
7816 else
7817 oldNewline += distance+1;
7818 }
7819 }
7820 // Split into lines and add each one individually
7821 oldNewline = 0;
7822
7823 {
7824 std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
7825
7826 while ( (distance = wrapAround.find(newline, oldNewline)) != wrapAround.npos)
7827 {
7828 distance -= oldNewline;
7829 m_deqMsgHistory.push_front(wrapAround.substr(oldNewline, distance));
7830 oldNewline += distance+1;
7831 }
7832 m_deqMsgHistory.push_front(wrapAround.substr(oldNewline));
7833 }
7834 }
7835
7836 const wchar_t* CConsole::GetBuffer()
7837 {
7838 m_szBuffer[m_iBufferLength] = 0;
7839 return( m_szBuffer );
7840 }
7841
7842 void CConsole::SetBuffer(const wchar_t* szMessage)
7843 {
7844 int oldBufferPos = m_iBufferPos; // remember since FlushBuffer will set it to 0
7845
7846 FlushBuffer();
7847
7848 wcsncpy(m_szBuffer, szMessage, CONSOLE_BUFFER_SIZE);
7849 m_szBuffer[CONSOLE_BUFFER_SIZE-1] = 0;
7850- m_iBufferLength = (int)wcslen(m_szBuffer);
7851+ m_iBufferLength = static_cast<int>(wcslen(m_szBuffer));
7852 m_iBufferPos = std::min(oldBufferPos, m_iBufferLength);
7853 }
7854
7855 void CConsole::UseHistoryFile(const VfsPath& filename, int max_history_lines)
7856 {
7857 m_MaxHistoryLines = max_history_lines;
7858
7859 m_sHistoryFile = filename;
7860 LoadHistory();
7861 }
7862
7863 void CConsole::ProcessBuffer(const wchar_t* szLine)
7864 {
7865 shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
7866 JSContext* cx = pScriptInterface->GetContext();
7867 JSAutoRequest rq(cx);
7868
7869 if (szLine == NULL) return;
7870 if (wcslen(szLine) <= 0) return;
7871
7872 ENSURE(wcslen(szLine) < CONSOLE_BUFFER_SIZE);
7873
7874 m_deqBufHistory.push_front(szLine);
7875 SaveHistory(); // Do this each line for the moment; if a script causes
7876 // a crash it's a useful record.
7877
7878 // Process it as JavaScript
7879
7880 JS::RootedValue rval(cx);
7881 pScriptInterface->Eval(szLine, &rval);
7882 if (!rval.isUndefined())
7883 InsertMessage(pScriptInterface->ToString(&rval));
7884 }
7885
7886 void CConsole::LoadHistory()
7887 {
7888 // note: we don't care if this file doesn't exist or can't be read;
7889 // just don't load anything in that case.
7890
7891 // do this before LoadFile to avoid an error message if file not found.
7892 if (!VfsFileExists(m_sHistoryFile))
7893 return;
7894
7895 shared_ptr<u8> buf; size_t buflen;
7896 if (g_VFS->LoadFile(m_sHistoryFile, buf, buflen) < 0)
7897 return;
7898
7899 CStr bytes ((char*)buf.get(), buflen);
7900
7901 CStrW str (bytes.FromUTF8());
7902 size_t pos = 0;
7903 while (pos != CStrW::npos)
7904 {
7905 pos = str.find('\n');
7906 if (pos != CStrW::npos)
7907 {
7908 if (pos > 0)
7909 m_deqBufHistory.push_front(str.Left(str[pos-1] == '\r' ? pos - 1 : pos));
7910 str = str.substr(pos + 1);
7911 }
7912 else if (str.length() > 0)
7913 m_deqBufHistory.push_front(str);
7914 }
7915 }
7916
7917 void CConsole::SaveHistory()
7918 {
7919 WriteBuffer buffer;
7920 const int linesToSkip = (int)m_deqBufHistory.size() - m_MaxHistoryLines;
7921 std::deque<std::wstring>::reverse_iterator it = m_deqBufHistory.rbegin();
7922 if(linesToSkip > 0)
7923 std::advance(it, linesToSkip);
7924 for (; it != m_deqBufHistory.rend(); ++it)
7925 {
7926 CStr8 line = CStrW(*it).ToUTF8();
7927 buffer.Append(line.data(), line.length());
7928 static const char newline = '\n';
7929 buffer.Append(&newline, 1);
7930 }
7931 g_VFS->CreateFile(m_sHistoryFile, buffer.Data(), buffer.Size());
7932 }
7933
7934 static bool isUnprintableChar(SDL_Keysym key)
7935 {
7936 switch (key.sym)
7937 {
7938 // We want to allow some, which are handled specially
7939 case SDLK_RETURN: case SDLK_TAB:
7940 case SDLK_BACKSPACE: case SDLK_DELETE:
7941 case SDLK_HOME: case SDLK_END:
7942 case SDLK_LEFT: case SDLK_RIGHT:
7943 case SDLK_UP: case SDLK_DOWN:
7944 case SDLK_PAGEUP: case SDLK_PAGEDOWN:
7945 return false;
7946
7947 // Ignore the others
7948 default:
7949 return true;
7950 }
7951 }
7952
7953 InReaction conInputHandler(const SDL_Event_* ev)
7954 {
7955 if (!g_Console)
7956 return IN_PASS;
7957
7958 if ((int)ev->ev.type == SDL_HOTKEYDOWN)
7959 {
7960 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
7961
7962 if (hotkey == "console.toggle")
7963 {
7964 g_Console->ToggleVisible();
7965 return IN_HANDLED;
7966 }
7967 else if (g_Console->IsActive() && hotkey == "copy")
7968 {
7969- sys_clipboard_set(g_Console->GetBuffer());
7970+ std::string text = utf8_from_wstring(g_Console->GetBuffer());
7971+ SDL_SetClipboardText(text.c_str());
7972 return IN_HANDLED;
7973 }
7974 else if (g_Console->IsActive() && hotkey == "paste")
7975 {
7976- wchar_t* text = sys_clipboard_get();
7977+ char* text = SDL_GetClipboardText();
7978 if (text)
7979 {
7980- for (wchar_t* c = text; *c; c++)
7981- g_Console->InsertChar(0, *c);
7982+ std::wstring wtext = wstring_from_utf8(text);
7983+ for (wchar_t c : wtext)
7984+ g_Console->InsertChar(0, c);
7985
7986- sys_clipboard_free(text);
7987+ SDL_free(text);
7988 }
7989 return IN_HANDLED;
7990 }
7991 }
7992
7993 if (!g_Console->IsActive())
7994 return IN_PASS;
7995
7996 // In SDL2, we no longer get Unicode wchars via SDL_Keysym
7997 // we use text input events instead and they provide UTF-8 chars
7998 if (ev->ev.type == SDL_TEXTINPUT && !HotkeyIsPressed("console.toggle"))
7999 {
8000 // TODO: this could be more efficient with an interface to insert UTF-8 strings directly
8001 std::wstring wstr = wstring_from_utf8(ev->ev.text.text);
8002 for (size_t i = 0; i < wstr.length(); ++i)
8003 g_Console->InsertChar(0, wstr[i]);
8004 return IN_HANDLED;
8005 }
8006 // TODO: text editing events for IME support
8007
8008 if (ev->ev.type != SDL_KEYDOWN)
8009 return IN_PASS;
8010
8011 int sym = ev->ev.key.keysym.sym;
8012
8013 // Stop unprintable characters (ctrl+, alt+ and escape),
8014 // also prevent ` and/or ~ appearing in console every time it's toggled.
8015 if (!isUnprintableChar(ev->ev.key.keysym) &&
8016 !HotkeyIsPressed("console.toggle"))
8017 {
8018 g_Console->InsertChar(sym, 0);
8019 return IN_HANDLED;
8020 }
8021
8022 return IN_PASS;
8023 }
8024Index: source/ps/CLogger.cpp
8025===================================================================
8026--- source/ps/CLogger.cpp (revision 23275)
8027+++ source/ps/CLogger.cpp (working copy)
8028@@ -1,341 +1,342 @@
8029 /* Copyright (C) 2019 Wildfire Games.
8030 * This file is part of 0 A.D.
8031 *
8032 * 0 A.D. is free software: you can redistribute it and/or modify
8033 * it under the terms of the GNU General Public License as published by
8034 * the Free Software Foundation, either version 2 of the License, or
8035 * (at your option) any later version.
8036 *
8037 * 0 A.D. is distributed in the hope that it will be useful,
8038 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8039 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8040 * GNU General Public License for more details.
8041 *
8042 * You should have received a copy of the GNU General Public License
8043 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
8044 */
8045
8046 #include "precompiled.h"
8047
8048 #include "CLogger.h"
8049
8050 #include "graphics/FontMetrics.h"
8051 #include "graphics/ShaderManager.h"
8052 #include "graphics/TextRenderer.h"
8053 #include "lib/ogl.h"
8054 #include "lib/timer.h"
8055 #include "lib/utf8.h"
8056 #include "ps/CConsole.h"
8057 #include "ps/Profile.h"
8058+#include "ps/Pyrogenesis.h"
8059 #include "renderer/Renderer.h"
8060
8061 #include <ctime>
8062 #include <iostream>
8063
8064 #include <boost/algorithm/string/replace.hpp>
8065
8066 CStrW g_UniqueLogPostfix;
8067 static const double RENDER_TIMEOUT = 10.0; // seconds before messages are deleted
8068 static const double RENDER_TIMEOUT_RATE = 10.0; // number of timed-out messages deleted per second
8069 static const size_t RENDER_LIMIT = 20; // maximum messages on screen at once
8070
8071 // Set up a default logger that throws everything away, because that's
8072 // better than crashing. (This is particularly useful for unit tests which
8073 // don't care about any log output.)
8074 struct BlackHoleStreamBuf : public std::streambuf
8075 {
8076 } blackHoleStreamBuf;
8077 std::ostream blackHoleStream(&blackHoleStreamBuf);
8078 CLogger nullLogger(&blackHoleStream, &blackHoleStream, false, true);
8079
8080 CLogger* g_Logger = &nullLogger;
8081
8082 const char* html_header0 =
8083 "<!DOCTYPE html>\n"
8084 "<meta charset=\"utf-8\">\n"
8085 "<title>Pyrogenesis Log</title>\n"
8086 "<style>"
8087 "body { background: #eee; color: black; font-family: sans-serif; } "
8088 "p { background: white; margin: 3px 0 3px 0; } "
8089 ".error { color: red; } "
8090 ".warning { color: blue; }"
8091 "</style>\n"
8092 "<h2>0 A.D. (";
8093
8094 const char* html_header1 = "</h2>\n";
8095
8096 CLogger::CLogger()
8097 {
8098 OsPath mainlogPath(psLogDir() / (L"mainlog" + g_UniqueLogPostfix + L".html"));
8099 m_MainLog = new std::ofstream(OsString(mainlogPath).c_str(), std::ofstream::out | std::ofstream::trunc);
8100 debug_printf("Writing the mainlog at %s\n", mainlogPath.string8().c_str());
8101
8102 OsPath interestinglogPath(psLogDir() / (L"interestinglog" + g_UniqueLogPostfix + L".html"));
8103 m_InterestingLog = new std::ofstream(OsString(interestinglogPath).c_str(), std::ofstream::out | std::ofstream::trunc);
8104
8105 m_OwnsStreams = true;
8106 m_UseDebugPrintf = true;
8107
8108 Init();
8109 }
8110
8111 CLogger::CLogger(std::ostream* mainLog, std::ostream* interestingLog, bool takeOwnership, bool useDebugPrintf)
8112 {
8113 m_MainLog = mainLog;
8114 m_InterestingLog = interestingLog;
8115 m_OwnsStreams = takeOwnership;
8116 m_UseDebugPrintf = useDebugPrintf;
8117
8118 Init();
8119 }
8120
8121 void CLogger::Init()
8122 {
8123 m_RenderLastEraseTime = -1.0;
8124 // this is called too early to allow us to call timer_Time(),
8125 // so we'll fill in the initial value later
8126
8127 m_NumberOfMessages = 0;
8128 m_NumberOfErrors = 0;
8129 m_NumberOfWarnings = 0;
8130
8131 *m_MainLog << html_header0 << engine_version << ") Main log" << html_header1;
8132 *m_InterestingLog << html_header0 << engine_version << ") Main log (warnings and errors only)" << html_header1;
8133 }
8134
8135 CLogger::~CLogger()
8136 {
8137 char buffer[128];
8138 sprintf_s(buffer, ARRAY_SIZE(buffer), " with %d message(s), %d error(s) and %d warning(s).", m_NumberOfMessages,m_NumberOfErrors,m_NumberOfWarnings);
8139
8140 time_t t = time(NULL);
8141 struct tm* now = localtime(&t);
8142 char currentDate[17];
8143 sprintf_s(currentDate, ARRAY_SIZE(currentDate), "%04d-%02d-%02d", 1900+now->tm_year, 1+now->tm_mon, now->tm_mday);
8144 char currentTime[10];
8145 sprintf_s(currentTime, ARRAY_SIZE(currentTime), "%02d:%02d:%02d", now->tm_hour, now->tm_min, now->tm_sec);
8146
8147 //Write closing text
8148
8149 *m_MainLog << "<p>Engine exited successfully on " << currentDate;
8150 *m_MainLog << " at " << currentTime << buffer << "</p>\n";
8151
8152 *m_InterestingLog << "<p>Engine exited successfully on " << currentDate;
8153 *m_InterestingLog << " at " << currentTime << buffer << "</p>\n";
8154
8155 if (m_OwnsStreams)
8156 {
8157 SAFE_DELETE(m_InterestingLog);
8158 SAFE_DELETE(m_MainLog);
8159 }
8160 }
8161
8162 static std::string ToHTML(const char* message)
8163 {
8164 std::string cmessage = message;
8165 boost::algorithm::replace_all(cmessage, "&", "&");
8166 boost::algorithm::replace_all(cmessage, "<", "<");
8167 return cmessage;
8168 }
8169
8170 void CLogger::WriteMessage(const char* message, bool doRender = false)
8171 {
8172 std::string cmessage = ToHTML(message);
8173
8174 std::lock_guard<std::mutex> lock(m_Mutex);
8175
8176 ++m_NumberOfMessages;
8177 // if (m_UseDebugPrintf)
8178 // debug_printf("MESSAGE: %s\n", message);
8179
8180 *m_MainLog << "<p>" << cmessage << "</p>\n";
8181 m_MainLog->flush();
8182
8183 if (doRender)
8184 {
8185 if (g_Console) g_Console->InsertMessage(std::string("INFO: ") + message);
8186
8187 PushRenderMessage(Normal, message);
8188 }
8189 }
8190
8191 void CLogger::WriteError(const char* message)
8192 {
8193 std::string cmessage = ToHTML(message);
8194
8195 std::lock_guard<std::mutex> lock(m_Mutex);
8196
8197 ++m_NumberOfErrors;
8198 if (m_UseDebugPrintf)
8199 debug_printf("ERROR: %.16000s\n", message);
8200
8201 if (g_Console) g_Console->InsertMessage(std::string("ERROR: ") + message);
8202 *m_InterestingLog << "<p class=\"error\">ERROR: " << cmessage << "</p>\n";
8203 m_InterestingLog->flush();
8204
8205 *m_MainLog << "<p class=\"error\">ERROR: " << cmessage << "</p>\n";
8206 m_MainLog->flush();
8207
8208 PushRenderMessage(Error, message);
8209 }
8210
8211 void CLogger::WriteWarning(const char* message)
8212 {
8213 std::string cmessage = ToHTML(message);
8214
8215 std::lock_guard<std::mutex> lock(m_Mutex);
8216
8217 ++m_NumberOfWarnings;
8218 if (m_UseDebugPrintf)
8219 debug_printf("WARNING: %s\n", message);
8220
8221 if (g_Console) g_Console->InsertMessage(std::string("WARNING: ") + message);
8222 *m_InterestingLog << "<p class=\"warning\">WARNING: " << cmessage << "</p>\n";
8223 m_InterestingLog->flush();
8224
8225 *m_MainLog << "<p class=\"warning\">WARNING: " << cmessage << "</p>\n";
8226 m_MainLog->flush();
8227
8228 PushRenderMessage(Warning, message);
8229 }
8230
8231 void CLogger::Render()
8232 {
8233 PROFILE3_GPU("logger");
8234
8235 CleanupRenderQueue();
8236
8237 CStrIntern font_name("mono-stroke-10");
8238 CFontMetrics font(font_name);
8239 int lineSpacing = font.GetLineSpacing();
8240
8241 CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
8242 textTech->BeginPass();
8243
8244 CTextRenderer textRenderer(textTech->GetShader());
8245 textRenderer.Font(font_name);
8246 textRenderer.Color(1.0f, 1.0f, 1.0f);
8247
8248 // Offset by an extra 35px vertically to avoid the top bar.
8249 textRenderer.Translate(4.0f, 35.0f + lineSpacing, 0.0f);
8250
8251 // (Lock must come after loading the CFont, since that might log error messages
8252 // and attempt to lock the mutex recursively which is forbidden)
8253 std::lock_guard<std::mutex> lock(m_Mutex);
8254
8255 for (const RenderedMessage& msg : m_RenderMessages)
8256 {
8257 const char* type;
8258 if (msg.method == Normal)
8259 {
8260 type = "info";
8261 textRenderer.Color(0.0f, 0.8f, 0.0f);
8262 }
8263 else if (msg.method == Warning)
8264 {
8265 type = "warning";
8266 textRenderer.Color(1.0f, 1.0f, 0.0f);
8267 }
8268 else
8269 {
8270 type = "error";
8271 textRenderer.Color(1.0f, 0.0f, 0.0f);
8272 }
8273
8274 CMatrix3D savedTransform = textRenderer.GetTransform();
8275
8276 textRenderer.PrintfAdvance(L"[%8.3f] %hs: ", msg.time, type);
8277 // Display the actual message in white so it's more readable
8278 textRenderer.Color(1.0f, 1.0f, 1.0f);
8279 textRenderer.Put(0.0f, 0.0f, msg.message.c_str());
8280
8281 textRenderer.SetTransform(savedTransform);
8282
8283 textRenderer.Translate(0.0f, (float)lineSpacing, 0.0f);
8284 }
8285
8286 textRenderer.Render();
8287
8288 textTech->EndPass();
8289 }
8290
8291 void CLogger::PushRenderMessage(ELogMethod method, const char* message)
8292 {
8293 double now = timer_Time();
8294
8295 // Add each message line separately
8296 const char* pos = message;
8297 const char* eol;
8298 while ((eol = strchr(pos, '\n')) != NULL)
8299 {
8300 if (eol != pos)
8301 {
8302 RenderedMessage r = { method, now, std::string(pos, eol) };
8303 m_RenderMessages.push_back(r);
8304 }
8305 pos = eol + 1;
8306 }
8307 // Add the last line, if we didn't end on a \n
8308 if (*pos != '\0')
8309 {
8310 RenderedMessage r = { method, now, std::string(pos) };
8311 m_RenderMessages.push_back(r);
8312 }
8313 }
8314
8315 void CLogger::CleanupRenderQueue()
8316 {
8317 std::lock_guard<std::mutex> lock(m_Mutex);
8318
8319 if (m_RenderMessages.empty())
8320 return;
8321
8322 double now = timer_Time();
8323
8324 // Initialise the timer on the first call (since we can't do it in the ctor)
8325 if (m_RenderLastEraseTime == -1.0)
8326 m_RenderLastEraseTime = now;
8327
8328 // Delete old messages, approximately at the given rate limit (and at most one per frame)
8329 if (now - m_RenderLastEraseTime > 1.0/RENDER_TIMEOUT_RATE)
8330 {
8331 if (m_RenderMessages[0].time + RENDER_TIMEOUT < now)
8332 {
8333 m_RenderMessages.pop_front();
8334 m_RenderLastEraseTime = now;
8335 }
8336 }
8337
8338 // If there's still too many then delete the oldest
8339 if (m_RenderMessages.size() > RENDER_LIMIT)
8340 m_RenderMessages.erase(m_RenderMessages.begin(), m_RenderMessages.end() - RENDER_LIMIT);
8341 }
8342
8343 TestLogger::TestLogger()
8344 {
8345 m_OldLogger = g_Logger;
8346 g_Logger = new CLogger(&m_Stream, &blackHoleStream, false, false);
8347 }
8348
8349 TestLogger::~TestLogger()
8350 {
8351 delete g_Logger;
8352 g_Logger = m_OldLogger;
8353 }
8354
8355 std::string TestLogger::GetOutput()
8356 {
8357 return m_Stream.str();
8358 }
8359
8360 TestStdoutLogger::TestStdoutLogger()
8361 {
8362 m_OldLogger = g_Logger;
8363 g_Logger = new CLogger(&std::cout, &blackHoleStream, false, false);
8364 }
8365
8366 TestStdoutLogger::~TestStdoutLogger()
8367 {
8368 delete g_Logger;
8369 g_Logger = m_OldLogger;
8370 }
8371Index: source/ps/GameSetup/GameSetup.cpp
8372===================================================================
8373--- source/ps/GameSetup/GameSetup.cpp (revision 23275)
8374+++ source/ps/GameSetup/GameSetup.cpp (working copy)
8375@@ -1,1680 +1,1660 @@
8376 /* Copyright (C) 2019 Wildfire Games.
8377 * This file is part of 0 A.D.
8378 *
8379 * 0 A.D. is free software: you can redistribute it and/or modify
8380 * it under the terms of the GNU General Public License as published by
8381 * the Free Software Foundation, either version 2 of the License, or
8382 * (at your option) any later version.
8383 *
8384 * 0 A.D. is distributed in the hope that it will be useful,
8385 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8386 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8387 * GNU General Public License for more details.
8388 *
8389 * You should have received a copy of the GNU General Public License
8390 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
8391 */
8392
8393 #include "precompiled.h"
8394
8395 #include "lib/app_hooks.h"
8396 #include "lib/config2.h"
8397 #include "lib/input.h"
8398 #include "lib/ogl.h"
8399 #include "lib/timer.h"
8400 #include "lib/external_libraries/libsdl.h"
8401 #include "lib/file/common/file_stats.h"
8402 #include "lib/res/h_mgr.h"
8403 #include "lib/res/graphics/cursor.h"
8404-#include "lib/sysdep/cursor.h"
8405
8406 #include "graphics/CinemaManager.h"
8407 #include "graphics/FontMetrics.h"
8408 #include "graphics/GameView.h"
8409 #include "graphics/LightEnv.h"
8410 #include "graphics/MapReader.h"
8411 #include "graphics/MaterialManager.h"
8412 #include "graphics/TerrainTextureManager.h"
8413 #include "gui/CGUI.h"
8414 #include "gui/GUIManager.h"
8415 #include "i18n/L10n.h"
8416 #include "maths/MathUtil.h"
8417 #include "network/NetServer.h"
8418 #include "network/NetClient.h"
8419 #include "network/NetMessage.h"
8420 #include "network/NetMessages.h"
8421
8422 #include "ps/CConsole.h"
8423 #include "ps/CLogger.h"
8424 #include "ps/ConfigDB.h"
8425 #include "ps/Filesystem.h"
8426 #include "ps/Game.h"
8427 #include "ps/GameSetup/Atlas.h"
8428 #include "ps/GameSetup/GameSetup.h"
8429 #include "ps/GameSetup/Paths.h"
8430 #include "ps/GameSetup/Config.h"
8431 #include "ps/GameSetup/CmdLineArgs.h"
8432 #include "ps/GameSetup/HWDetect.h"
8433 #include "ps/Globals.h"
8434 #include "ps/Hotkey.h"
8435 #include "ps/Joystick.h"
8436 #include "ps/Loader.h"
8437 #include "ps/Mod.h"
8438 #include "ps/ModIo.h"
8439 #include "ps/Profile.h"
8440 #include "ps/ProfileViewer.h"
8441 #include "ps/Profiler2.h"
8442 #include "ps/Pyrogenesis.h" // psSetLogDir
8443 #include "ps/scripting/JSInterface_Console.h"
8444 #include "ps/TouchInput.h"
8445 #include "ps/UserReport.h"
8446 #include "ps/Util.h"
8447 #include "ps/VideoMode.h"
8448 #include "ps/VisualReplay.h"
8449 #include "ps/World.h"
8450
8451 #include "renderer/Renderer.h"
8452 #include "renderer/VertexBufferManager.h"
8453 #include "renderer/ModelRenderer.h"
8454 #include "scriptinterface/ScriptInterface.h"
8455 #include "scriptinterface/ScriptStats.h"
8456 #include "scriptinterface/ScriptConversions.h"
8457 #include "scriptinterface/ScriptRuntime.h"
8458 #include "simulation2/Simulation2.h"
8459 #include "lobby/IXmppClient.h"
8460 #include "soundmanager/scripting/JSInterface_Sound.h"
8461 #include "soundmanager/ISoundManager.h"
8462 #include "tools/atlas/GameInterface/GameLoop.h"
8463 #include "tools/atlas/GameInterface/View.h"
8464
8465-#if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets
8466-#define MUST_INIT_X11 1
8467-#include <X11/Xlib.h>
8468-#else
8469-#define MUST_INIT_X11 0
8470-#endif
8471-
8472 extern void RestartEngine();
8473
8474 #include <iostream>
8475
8476 #include <boost/algorithm/string/classification.hpp>
8477 #include <boost/algorithm/string/split.hpp>
8478
8479 ERROR_GROUP(System);
8480 ERROR_TYPE(System, SDLInitFailed);
8481 ERROR_TYPE(System, VmodeFailed);
8482 ERROR_TYPE(System, RequiredExtensionsMissing);
8483
8484 bool g_DoRenderGui = true;
8485 bool g_DoRenderLogger = true;
8486 bool g_DoRenderCursor = true;
8487
8488 shared_ptr<ScriptRuntime> g_ScriptRuntime;
8489
8490 static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code
8491
8492 bool g_InDevelopmentCopy;
8493 bool g_CheckedIfInDevelopmentCopy = false;
8494
8495 static void SetTextureQuality(int quality)
8496 {
8497 int q_flags;
8498 GLint filter;
8499
8500 retry:
8501 // keep this in sync with SANE_TEX_QUALITY_DEFAULT
8502 switch(quality)
8503 {
8504 // worst quality
8505 case 0:
8506 q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP;
8507 filter = GL_NEAREST;
8508 break;
8509 // [perf] add bilinear filtering
8510 case 1:
8511 q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP;
8512 filter = GL_LINEAR;
8513 break;
8514 // [vmem] no longer reduce resolution
8515 case 2:
8516 q_flags = OGL_TEX_HALF_BPP;
8517 filter = GL_LINEAR;
8518 break;
8519 // [vmem] add mipmaps
8520 case 3:
8521 q_flags = OGL_TEX_HALF_BPP;
8522 filter = GL_NEAREST_MIPMAP_LINEAR;
8523 break;
8524 // [perf] better filtering
8525 case 4:
8526 q_flags = OGL_TEX_HALF_BPP;
8527 filter = GL_LINEAR_MIPMAP_LINEAR;
8528 break;
8529 // [vmem] no longer reduce bpp
8530 case SANE_TEX_QUALITY_DEFAULT:
8531 q_flags = OGL_TEX_FULL_QUALITY;
8532 filter = GL_LINEAR_MIPMAP_LINEAR;
8533 break;
8534 // [perf] add anisotropy
8535 case 6:
8536 // TODO: add anisotropic filtering
8537 q_flags = OGL_TEX_FULL_QUALITY;
8538 filter = GL_LINEAR_MIPMAP_LINEAR;
8539 break;
8540 // invalid
8541 default:
8542 debug_warn(L"SetTextureQuality: invalid quality");
8543 quality = SANE_TEX_QUALITY_DEFAULT;
8544 // careful: recursion doesn't work and we don't want to duplicate
8545 // the "sane" default values.
8546 goto retry;
8547 }
8548
8549 ogl_tex_set_defaults(q_flags, filter);
8550 }
8551
8552
8553 //----------------------------------------------------------------------------
8554 // GUI integration
8555 //----------------------------------------------------------------------------
8556
8557 // display progress / description in loading screen
8558 void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task)
8559 {
8560 const ScriptInterface& scriptInterface = *(g_GUI->GetActiveGUI()->GetScriptInterface());
8561 JSContext* cx = scriptInterface.GetContext();
8562 JSAutoRequest rq(cx);
8563
8564 JS::AutoValueVector paramData(cx);
8565
8566 paramData.append(JS::NumberValue(percent));
8567
8568 JS::RootedValue valPendingTask(cx);
8569 scriptInterface.ToJSVal(cx, &valPendingTask, pending_task);
8570 paramData.append(valPendingTask);
8571
8572 g_GUI->SendEventToAll("GameLoadProgress", paramData);
8573 }
8574
8575 bool ShouldRender()
8576 {
8577 return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen());
8578 }
8579
8580
8581 void Render()
8582 {
8583 // Do not render if not focused while in fullscreen or minimised,
8584 // as that triggers a difficult-to-reproduce crash on some graphic cards.
8585 if (!ShouldRender())
8586 return;
8587
8588 PROFILE3("render");
8589
8590 ogl_WarnIfError();
8591
8592 g_Profiler2.RecordGPUFrameStart();
8593
8594 ogl_WarnIfError();
8595
8596 // prepare before starting the renderer frame
8597 if (g_Game && g_Game->IsGameStarted())
8598 g_Game->GetView()->BeginFrame();
8599
8600 if (g_Game)
8601 g_Renderer.SetSimulation(g_Game->GetSimulation2());
8602
8603 // start new frame
8604 g_Renderer.BeginFrame();
8605
8606 ogl_WarnIfError();
8607
8608 if (g_Game && g_Game->IsGameStarted())
8609 g_Game->GetView()->Render();
8610
8611 ogl_WarnIfError();
8612
8613 g_Renderer.RenderTextOverlays();
8614
8615 // If we're in Atlas game view, render special tools
8616 if (g_AtlasGameLoop && g_AtlasGameLoop->view)
8617 {
8618 g_AtlasGameLoop->view->DrawCinemaPathTool();
8619 ogl_WarnIfError();
8620 }
8621
8622 if (g_Game && g_Game->IsGameStarted())
8623 g_Game->GetView()->GetCinema()->Render();
8624
8625 ogl_WarnIfError();
8626
8627 if (g_DoRenderGui)
8628 g_GUI->Draw();
8629
8630 ogl_WarnIfError();
8631
8632 // If we're in Atlas game view, render special overlays (e.g. editor bandbox)
8633 if (g_AtlasGameLoop && g_AtlasGameLoop->view)
8634 {
8635 g_AtlasGameLoop->view->DrawOverlays();
8636 ogl_WarnIfError();
8637 }
8638
8639 // Text:
8640
8641 glDisable(GL_DEPTH_TEST);
8642
8643 g_Console->Render();
8644
8645 ogl_WarnIfError();
8646
8647 if (g_DoRenderLogger)
8648 g_Logger->Render();
8649
8650 ogl_WarnIfError();
8651
8652 // Profile information
8653
8654 g_ProfileViewer.RenderProfile();
8655
8656 ogl_WarnIfError();
8657
8658 // Draw the cursor (or set the Windows cursor, on Windows)
8659 if (g_DoRenderCursor)
8660 {
8661 PROFILE3_GPU("cursor");
8662 CStrW cursorName = g_CursorName;
8663 if (cursorName.empty())
8664 {
8665 cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y, g_GuiScale, false);
8666 }
8667 else
8668 {
8669 bool forceGL = false;
8670 CFG_GET_VAL("nohwcursor", forceGL);
8671
8672 #if CONFIG2_GLES
8673 #warning TODO: implement cursors for GLES
8674 #else
8675 // set up transform for GL cursor
8676 glMatrixMode(GL_PROJECTION);
8677 glPushMatrix();
8678 glLoadIdentity();
8679 glMatrixMode(GL_MODELVIEW);
8680 glPushMatrix();
8681 glLoadIdentity();
8682 CMatrix3D transform;
8683 transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f);
8684 glLoadMatrixf(&transform._11);
8685 #endif
8686
8687 #if OS_ANDROID
8688 #warning TODO: cursors for Android
8689 #else
8690 if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y, g_GuiScale, forceGL) < 0)
8691 LOGWARNING("Failed to draw cursor '%s'", utf8_from_wstring(cursorName));
8692 #endif
8693
8694 #if CONFIG2_GLES
8695 #warning TODO: implement cursors for GLES
8696 #else
8697 // restore transform
8698 glMatrixMode(GL_PROJECTION);
8699 glPopMatrix();
8700 glMatrixMode(GL_MODELVIEW);
8701 glPopMatrix();
8702 #endif
8703 }
8704 }
8705
8706 glEnable(GL_DEPTH_TEST);
8707
8708 g_Renderer.EndFrame();
8709
8710 PROFILE2_ATTR("draw calls: %d", (int)g_Renderer.GetStats().m_DrawCalls);
8711 PROFILE2_ATTR("terrain tris: %d", (int)g_Renderer.GetStats().m_TerrainTris);
8712 PROFILE2_ATTR("water tris: %d", (int)g_Renderer.GetStats().m_WaterTris);
8713 PROFILE2_ATTR("model tris: %d", (int)g_Renderer.GetStats().m_ModelTris);
8714 PROFILE2_ATTR("overlay tris: %d", (int)g_Renderer.GetStats().m_OverlayTris);
8715 PROFILE2_ATTR("blend splats: %d", (int)g_Renderer.GetStats().m_BlendSplats);
8716 PROFILE2_ATTR("particles: %d", (int)g_Renderer.GetStats().m_Particles);
8717
8718 ogl_WarnIfError();
8719
8720 g_Profiler2.RecordGPUFrameEnd();
8721
8722 ogl_WarnIfError();
8723 }
8724
8725 ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags))
8726 {
8727 // If we're fullscreen, then sometimes (at least on some particular drivers on Linux)
8728 // displaying the error dialog hangs the desktop since the dialog box is behind the
8729 // fullscreen window. So we just force the game to windowed mode before displaying the dialog.
8730 // (But only if we're in the main thread, and not if we're being reentrant.)
8731 if (ThreadUtil::IsMainThread())
8732 {
8733 static bool reentering = false;
8734 if (!reentering)
8735 {
8736 reentering = true;
8737 g_VideoMode.SetFullscreen(false);
8738 reentering = false;
8739 }
8740 }
8741
8742 // We don't actually implement the error display here, so return appropriately
8743 return ERI_NOT_IMPLEMENTED;
8744 }
8745
8746 const std::vector<CStr>& GetMods(const CmdLineArgs& args, int flags)
8747 {
8748 const bool init_mods = (flags & INIT_MODS) == INIT_MODS;
8749 const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod");
8750 const bool add_public = (flags & INIT_MODS_PUBLIC) == INIT_MODS_PUBLIC;
8751
8752 if (!init_mods)
8753 {
8754 // Add the user mod if it should be present
8755 if (add_user && (g_modsLoaded.empty() || g_modsLoaded.back() != "user"))
8756 g_modsLoaded.push_back("user");
8757
8758 return g_modsLoaded;
8759 }
8760
8761 g_modsLoaded = args.GetMultiple("mod");
8762
8763 if (add_public)
8764 g_modsLoaded.insert(g_modsLoaded.begin(), "public");
8765
8766 g_modsLoaded.insert(g_modsLoaded.begin(), "mod");
8767
8768 // Add the user mod if not explicitly disabled or we have a dev copy so
8769 // that saved files end up in version control and not in the user mod.
8770 if (add_user)
8771 g_modsLoaded.push_back("user");
8772
8773 return g_modsLoaded;
8774 }
8775
8776 void MountMods(const Paths& paths, const std::vector<CStr>& mods)
8777 {
8778 OsPath modPath = paths.RData()/"mods";
8779 OsPath modUserPath = paths.UserData()/"mods";
8780 for (size_t i = 0; i < mods.size(); ++i)
8781 {
8782 size_t priority = (i+1)*2; // mods are higher priority than regular mountings, which default to priority 0
8783 size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_REPLACEABLE;
8784 size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST;
8785
8786 OsPath modName(mods[i]);
8787 if (InDevelopmentCopy())
8788 {
8789 // We are running a dev copy, so only mount mods in the user mod path
8790 // if the mod does not exist in the data path.
8791 if (DirectoryExists(modPath / modName/""))
8792 g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
8793 else
8794 g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority);
8795 }
8796 else
8797 {
8798 g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
8799 // Ensure that user modified files are loaded, if they are present
8800 g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority+1);
8801 }
8802 }
8803 }
8804
8805 static void InitVfs(const CmdLineArgs& args, int flags)
8806 {
8807 TIMER(L"InitVfs");
8808
8809 const bool setup_error = (flags & INIT_HAVE_DISPLAY_ERROR) == 0;
8810
8811 const Paths paths(args);
8812
8813 OsPath logs(paths.Logs());
8814 CreateDirectories(logs, 0700);
8815
8816 psSetLogDir(logs);
8817 // desired location for crashlog is now known. update AppHooks ASAP
8818 // (particularly before the following error-prone operations):
8819 AppHooks hooks = {0};
8820 hooks.bundle_logs = psBundleLogs;
8821 hooks.get_log_dir = psLogDir;
8822 if (setup_error)
8823 hooks.display_error = psDisplayError;
8824 app_hooks_update(&hooks);
8825
8826 g_VFS = CreateVfs();
8827
8828 const OsPath readonlyConfig = paths.RData()/"config"/"";
8829 g_VFS->Mount(L"config/", readonlyConfig);
8830
8831 // Engine localization files.
8832 g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/"");
8833
8834 MountMods(paths, GetMods(args, flags));
8835
8836 // We mount these dirs last as otherwise writing could result in files being placed in a mod's dir.
8837 g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"");
8838 g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH);
8839 // Mounting with highest priority, so that a mod supplied user.cfg is harmless
8840 g_VFS->Mount(L"config/", readonlyConfig, 0, (size_t)-1);
8841 if(readonlyConfig != paths.Config())
8842 g_VFS->Mount(L"config/", paths.Config(), 0, (size_t)-1);
8843
8844 g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads)
8845
8846 // note: don't bother with g_VFS->TextRepresentation - directories
8847 // haven't yet been populated and are empty.
8848 }
8849
8850
8851 static void InitPs(bool setup_gui, const CStrW& gui_page, ScriptInterface* srcScriptInterface, JS::HandleValue initData)
8852 {
8853 {
8854 // console
8855 TIMER(L"ps_console");
8856
8857 g_Console->UpdateScreenSize(g_xres, g_yres);
8858
8859 // Calculate and store the line spacing
8860 CFontMetrics font(CStrIntern(CONSOLE_FONT));
8861 g_Console->m_iFontHeight = font.GetLineSpacing();
8862 g_Console->m_iFontWidth = font.GetCharacterWidth(L'C');
8863 g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth);
8864 // Offset by an arbitrary amount, to make it fit more nicely
8865 g_Console->m_iFontOffset = 7;
8866
8867 double blinkRate = 0.5;
8868 CFG_GET_VAL("gui.cursorblinkrate", blinkRate);
8869 g_Console->SetCursorBlinkRate(blinkRate);
8870 }
8871
8872 // hotkeys
8873 {
8874 TIMER(L"ps_lang_hotkeys");
8875 LoadHotkeys();
8876 }
8877
8878 if (!setup_gui)
8879 {
8880 // We do actually need *some* kind of GUI loaded, so use the
8881 // (currently empty) Atlas one
8882 g_GUI->SwitchPage(L"page_atlas.xml", srcScriptInterface, initData);
8883 return;
8884 }
8885
8886 // GUI uses VFS, so this must come after VFS init.
8887 g_GUI->SwitchPage(gui_page, srcScriptInterface, initData);
8888 }
8889
8890 void InitPsAutostart(bool networked, JS::HandleValue attrs)
8891 {
8892 // The GUI has not been initialized yet, so use the simulation scriptinterface for this variable
8893 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
8894 JSContext* cx = scriptInterface.GetContext();
8895 JSAutoRequest rq(cx);
8896
8897 JS::RootedValue playerAssignments(cx);
8898 ScriptInterface::CreateObject(cx, &playerAssignments);
8899
8900 if (!networked)
8901 {
8902 JS::RootedValue localPlayer(cx);
8903 ScriptInterface::CreateObject(cx, &localPlayer, "player", g_Game->GetPlayerID());
8904 scriptInterface.SetProperty(playerAssignments, "local", localPlayer);
8905 }
8906
8907 JS::RootedValue sessionInitData(cx);
8908
8909 ScriptInterface::CreateObject(
8910 cx,
8911 &sessionInitData,
8912 "attribs", attrs,
8913 "playerAssignments", playerAssignments);
8914
8915 InitPs(true, L"page_loading.xml", &scriptInterface, sessionInitData);
8916 }
8917
8918
8919 void InitInput()
8920 {
8921 g_Joystick.Initialise();
8922
8923 // register input handlers
8924 // This stack is constructed so the first added, will be the last
8925 // one called. This is important, because each of the handlers
8926 // has the potential to block events to go further down
8927 // in the chain. I.e. the last one in the list added, is the
8928 // only handler that can block all messages before they are
8929 // processed.
8930 in_add_handler(game_view_handler);
8931
8932 in_add_handler(CProfileViewer::InputThunk);
8933
8934 in_add_handler(conInputHandler);
8935
8936 in_add_handler(HotkeyInputHandler);
8937
8938 // gui_handler needs to be registered after (i.e. called before!) the
8939 // hotkey handler so that input boxes can be typed in without
8940 // setting off hotkeys.
8941 in_add_handler(gui_handler);
8942
8943 in_add_handler(touch_input_handler);
8944
8945 // must be registered after (called before) the GUI which relies on these globals
8946 in_add_handler(GlobalsInputHandler);
8947
8948 // Should be called first, this updates our hotkey press state
8949 // so that js calls to HotkeyIsPressed are synched with events.
8950 in_add_handler(HotkeyStateChange);
8951 }
8952
8953
8954 static void ShutdownPs()
8955 {
8956 SAFE_DELETE(g_GUI);
8957
8958 UnloadHotkeys();
8959
8960 // disable the special Windows cursor, or free textures for OGL cursors
8961 cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y, 1.0, false);
8962 }
8963
8964
8965 static void InitRenderer()
8966 {
8967 TIMER(L"InitRenderer");
8968
8969 if(g_NoGLS3TC)
8970 ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE);
8971 if(g_NoGLAutoMipmap)
8972 ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE);
8973
8974 // create renderer
8975 new CRenderer;
8976
8977 g_RenderingOptions.ReadConfig();
8978
8979 // set renderer options from command line options - NOVBO must be set before opening the renderer
8980 // and init them in the ConfigDB when needed
8981 g_RenderingOptions.SetNoVBO(g_NoGLVBO);
8982 g_RenderingOptions.SetShadows(g_Shadows);
8983 g_ConfigDB.SetValueBool(CFG_SYSTEM, "shadows", g_Shadows);
8984
8985 g_RenderingOptions.SetWaterEffects(g_WaterEffects);
8986 g_ConfigDB.SetValueBool(CFG_SYSTEM, "watereffects", g_WaterEffects);
8987 g_RenderingOptions.SetWaterFancyEffects(g_WaterFancyEffects);
8988 g_ConfigDB.SetValueBool(CFG_SYSTEM, "waterfancyeffects", g_WaterFancyEffects);
8989 g_RenderingOptions.SetWaterRealDepth(g_WaterRealDepth);
8990 g_ConfigDB.SetValueBool(CFG_SYSTEM, "waterrealdepth", g_WaterRealDepth);
8991 g_RenderingOptions.SetWaterReflection(g_WaterReflection);
8992 g_ConfigDB.SetValueBool(CFG_SYSTEM, "waterreflection", g_WaterReflection);
8993 g_RenderingOptions.SetWaterRefraction(g_WaterRefraction);
8994 g_ConfigDB.SetValueBool(CFG_SYSTEM, "waterrefraction", g_WaterRefraction);
8995 g_RenderingOptions.SetWaterShadows(g_WaterShadows);
8996 g_ConfigDB.SetValueBool(CFG_SYSTEM, "watershadows", g_WaterShadows);
8997
8998 g_RenderingOptions.SetRenderPath(RenderPathEnum::FromString(g_RenderPath));
8999 g_RenderingOptions.SetShadowPCF(g_ShadowPCF);
9000 g_ConfigDB.SetValueBool(CFG_SYSTEM, "shadowpcf", g_ShadowPCF);
9001 g_RenderingOptions.SetParticles(g_Particles);
9002 g_ConfigDB.SetValueBool(CFG_SYSTEM, "particles", g_Particles);
9003 g_RenderingOptions.SetFog(g_Fog);
9004 g_ConfigDB.SetValueBool(CFG_SYSTEM, "fog", g_Fog);
9005 g_RenderingOptions.SetSilhouettes(g_Silhouettes);
9006 g_ConfigDB.SetValueBool(CFG_SYSTEM, "silhouettes", g_Silhouettes);
9007 g_RenderingOptions.SetShowSky(g_ShowSky);
9008 g_ConfigDB.SetValueBool(CFG_SYSTEM, "showsky", g_ShowSky);
9009 g_RenderingOptions.SetPreferGLSL(g_PreferGLSL);
9010 g_ConfigDB.SetValueBool(CFG_SYSTEM, "preferglsl", g_PreferGLSL);
9011 g_RenderingOptions.SetPostProc(g_PostProc);
9012 g_ConfigDB.SetValueBool(CFG_SYSTEM, "postproc", g_PostProc);
9013 g_RenderingOptions.SetSmoothLOS(g_SmoothLOS);
9014 g_ConfigDB.SetValueBool(CFG_SYSTEM, "smoothlos", g_SmoothLOS);
9015
9016 // create terrain related stuff
9017 new CTerrainTextureManager;
9018
9019 g_Renderer.Open(g_xres, g_yres);
9020
9021 // Setup lighting environment. Since the Renderer accesses the
9022 // lighting environment through a pointer, this has to be done before
9023 // the first Frame.
9024 g_Renderer.SetLightEnv(&g_LightEnv);
9025
9026 // I haven't seen the camera affecting GUI rendering and such, but the
9027 // viewport has to be updated according to the video mode
9028 SViewPort vp;
9029 vp.m_X = 0;
9030 vp.m_Y = 0;
9031 vp.m_Width = g_xres;
9032 vp.m_Height = g_yres;
9033 g_Renderer.SetViewport(vp);
9034
9035 ColorActivateFastImpl();
9036 ModelRenderer::Init();
9037 }
9038
9039 static void InitSDL()
9040 {
9041 #if OS_LINUX
9042 // In fullscreen mode when SDL is compiled with DGA support, the mouse
9043 // sensitivity often appears to be unusably wrong (typically too low).
9044 // (This seems to be reported almost exclusively on Ubuntu, but can be
9045 // reproduced on Gentoo after explicitly enabling DGA.)
9046 // Disabling the DGA mouse appears to fix that problem, and doesn't
9047 // have any obvious negative effects.
9048 setenv("SDL_VIDEO_X11_DGAMOUSE", "0", 0);
9049 #endif
9050
9051 if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0)
9052 {
9053 LOGERROR("SDL library initialization failed: %s", SDL_GetError());
9054 throw PSERROR_System_SDLInitFailed();
9055 }
9056 atexit(SDL_Quit);
9057
9058 // Text input is active by default, disable it until it is actually needed.
9059 SDL_StopTextInput();
9060
9061 #if OS_MACOSX
9062 // Some Mac mice only have one button, so they can't right-click
9063 // but SDL2 can emulate that with Ctrl+Click
9064 bool macMouse = false;
9065 CFG_GET_VAL("macmouse", macMouse);
9066 SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, macMouse ? "1" : "0");
9067 #endif
9068 }
9069
9070 static void ShutdownSDL()
9071 {
9072 SDL_Quit();
9073- sys_cursor_reset();
9074 }
9075
9076
9077 void EndGame()
9078 {
9079 SAFE_DELETE(g_NetClient);
9080 SAFE_DELETE(g_NetServer);
9081 SAFE_DELETE(g_Game);
9082
9083 if (CRenderer::IsInitialised())
9084 {
9085 ISoundManager::CloseGame();
9086 g_Renderer.ResetState();
9087 }
9088 }
9089
9090 void Shutdown(int flags)
9091 {
9092 const bool hasRenderer = CRenderer::IsInitialised();
9093
9094 if ((flags & SHUTDOWN_FROM_CONFIG))
9095 goto from_config;
9096
9097 EndGame();
9098
9099 SAFE_DELETE(g_XmppClient);
9100
9101 SAFE_DELETE(g_ModIo);
9102
9103 ShutdownPs();
9104
9105 TIMER_BEGIN(L"shutdown TexMan");
9106 delete &g_TexMan;
9107 TIMER_END(L"shutdown TexMan");
9108
9109 if (hasRenderer)
9110 {
9111 TIMER_BEGIN(L"shutdown Renderer");
9112 g_Renderer.~CRenderer();
9113 g_VBMan.Shutdown();
9114 TIMER_END(L"shutdown Renderer");
9115 }
9116
9117 g_Profiler2.ShutdownGPU();
9118
9119 // Free cursors before shutting down SDL, as they may depend on SDL.
9120 cursor_shutdown();
9121
9122 TIMER_BEGIN(L"shutdown SDL");
9123 ShutdownSDL();
9124 TIMER_END(L"shutdown SDL");
9125
9126 if (hasRenderer)
9127 g_VideoMode.Shutdown();
9128
9129 TIMER_BEGIN(L"shutdown UserReporter");
9130 g_UserReporter.Deinitialize();
9131 TIMER_END(L"shutdown UserReporter");
9132
9133 // Cleanup curl now that g_ModIo and g_UserReporter have been shutdown.
9134 curl_global_cleanup();
9135
9136 delete &g_L10n;
9137
9138 from_config:
9139 TIMER_BEGIN(L"shutdown ConfigDB");
9140 delete &g_ConfigDB;
9141 TIMER_END(L"shutdown ConfigDB");
9142
9143 SAFE_DELETE(g_Console);
9144
9145 // This is needed to ensure that no callbacks from the JSAPI try to use
9146 // the profiler when it's already destructed
9147 g_ScriptRuntime.reset();
9148
9149 // resource
9150 // first shut down all resource owners, and then the handle manager.
9151 TIMER_BEGIN(L"resource modules");
9152
9153 ISoundManager::SetEnabled(false);
9154
9155 g_VFS.reset();
9156
9157 // this forcibly frees all open handles (thus preventing real leaks),
9158 // and makes further access to h_mgr impossible.
9159 h_mgr_shutdown();
9160
9161 file_stats_dump();
9162
9163 TIMER_END(L"resource modules");
9164
9165 TIMER_BEGIN(L"shutdown misc");
9166 timer_DisplayClientTotals();
9167
9168 CNetHost::Deinitialize();
9169
9170 // should be last, since the above use them
9171 SAFE_DELETE(g_Logger);
9172 delete &g_Profiler;
9173 delete &g_ProfileViewer;
9174
9175 SAFE_DELETE(g_ScriptStatsTable);
9176 TIMER_END(L"shutdown misc");
9177 }
9178
9179 #if OS_UNIX
9180 static void FixLocales()
9181 {
9182 #if OS_MACOSX || OS_BSD
9183 // OS X requires a UTF-8 locale in LC_CTYPE so that *wprintf can handle
9184 // wide characters. Peculiarly the string "UTF-8" seems to be acceptable
9185 // despite not being a real locale, and it's conveniently language-agnostic,
9186 // so use that.
9187 setlocale(LC_CTYPE, "UTF-8");
9188 #endif
9189
9190
9191 // On misconfigured systems with incorrect locale settings, we'll die
9192 // with a C++ exception when some code (e.g. Boost) tries to use locales.
9193 // To avoid death, we'll detect the problem here and warn the user and
9194 // reset to the default C locale.
9195
9196
9197 // For informing the user of the problem, use the list of env vars that
9198 // glibc setlocale looks at. (LC_ALL is checked first, and LANG last.)
9199 const char* const LocaleEnvVars[] = {
9200 "LC_ALL",
9201 "LC_COLLATE",
9202 "LC_CTYPE",
9203 "LC_MONETARY",
9204 "LC_NUMERIC",
9205 "LC_TIME",
9206 "LC_MESSAGES",
9207 "LANG"
9208 };
9209
9210 try
9211 {
9212 // this constructor is similar to setlocale(LC_ALL, ""),
9213 // but instead of returning NULL, it throws runtime_error
9214 // when the first locale env variable found contains an invalid value
9215 std::locale("");
9216 }
9217 catch (std::runtime_error&)
9218 {
9219 LOGWARNING("Invalid locale settings");
9220
9221 for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++)
9222 {
9223 if (char* envval = getenv(LocaleEnvVars[i]))
9224 LOGWARNING(" %s=\"%s\"", LocaleEnvVars[i], envval);
9225 else
9226 LOGWARNING(" %s=\"(unset)\"", LocaleEnvVars[i]);
9227 }
9228
9229 // We should set LC_ALL since it overrides LANG
9230 if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1))
9231 debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable.");
9232 else
9233 LOGWARNING("Setting LC_ALL env variable to: %s", getenv("LC_ALL"));
9234 }
9235 }
9236 #else
9237 static void FixLocales()
9238 {
9239 // Do nothing on Windows
9240 }
9241 #endif
9242
9243 void EarlyInit()
9244 {
9245 // If you ever want to catch a particular allocation:
9246 //_CrtSetBreakAlloc(232647);
9247
9248 ThreadUtil::SetMainThread();
9249
9250 debug_SetThreadName("main");
9251 // add all debug_printf "tags" that we are interested in:
9252 debug_filter_add("TIMER");
9253
9254 timer_LatchStartTime();
9255
9256 // initialise profiler early so it can profile startup,
9257 // but only after LatchStartTime
9258 g_Profiler2.Initialise();
9259
9260 FixLocales();
9261
9262- // Because we do GL calls from a secondary thread, Xlib needs to
9263- // be told to support multiple threads safely.
9264- // This is needed for Atlas, but we have to call it before any other
9265- // Xlib functions (e.g. the ones used when drawing the main menu
9266- // before launching Atlas)
9267-#if MUST_INIT_X11
9268- int status = XInitThreads();
9269- if (status == 0)
9270- debug_printf("Error enabling thread-safety via XInitThreads\n");
9271-#endif
9272-
9273 // Initialise the low-quality rand function
9274 srand(time(NULL)); // NOTE: this rand should *not* be used for simulation!
9275 }
9276
9277 bool Autostart(const CmdLineArgs& args);
9278
9279 /**
9280 * Returns true if the user has intended to start a visual replay from command line.
9281 */
9282 bool AutostartVisualReplay(const std::string& replayFile);
9283
9284 bool Init(const CmdLineArgs& args, int flags)
9285 {
9286 h_mgr_init();
9287
9288 // Do this as soon as possible, because it chdirs
9289 // and will mess up the error reporting if anything
9290 // crashes before the working directory is set.
9291 InitVfs(args, flags);
9292
9293 // This must come after VFS init, which sets the current directory
9294 // (required for finding our output log files).
9295 g_Logger = new CLogger;
9296
9297 new CProfileViewer;
9298 new CProfileManager; // before any script code
9299
9300 g_ScriptStatsTable = new CScriptStatsTable;
9301 g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
9302
9303 // Set up the console early, so that debugging
9304 // messages can be logged to it. (The console's size
9305 // and fonts are set later in InitPs())
9306 g_Console = new CConsole();
9307
9308 // g_ConfigDB, command line args, globals
9309 CONFIG_Init(args);
9310
9311 // Using a global object for the runtime is a workaround until Simulation and AI use
9312 // their own threads and also their own runtimes.
9313 const int runtimeSize = 384 * 1024 * 1024;
9314 const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024;
9315 g_ScriptRuntime = ScriptInterface::CreateRuntime(shared_ptr<ScriptRuntime>(), runtimeSize, heapGrowthBytesGCTrigger);
9316
9317 Mod::CacheEnabledModVersions(g_ScriptRuntime);
9318
9319 // Special command-line mode to dump the entity schemas instead of running the game.
9320 // (This must be done after loading VFS etc, but should be done before wasting time
9321 // on anything else.)
9322 if (args.Has("dumpSchema"))
9323 {
9324 CSimulation2 sim(NULL, g_ScriptRuntime, NULL);
9325 sim.LoadDefaultScripts();
9326 std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc);
9327 f << sim.GenerateSchema();
9328 std::cout << "Generated entity.rng\n";
9329 exit(0);
9330 }
9331
9332 CNetHost::Initialize();
9333
9334 #if CONFIG2_AUDIO
9335 if (!args.Has("autostart-nonvisual"))
9336 ISoundManager::CreateSoundManager();
9337 #endif
9338
9339 // Check if there are mods specified on the command line,
9340 // or if we already set the mods (~INIT_MODS),
9341 // else check if there are mods that should be loaded specified
9342 // in the config and load those (by aborting init and restarting
9343 // the engine).
9344 if (!args.Has("mod") && (flags & INIT_MODS) == INIT_MODS)
9345 {
9346 CStr modstring;
9347 CFG_GET_VAL("mod.enabledmods", modstring);
9348 if (!modstring.empty())
9349 {
9350 std::vector<CStr> mods;
9351 boost::split(mods, modstring, boost::is_any_of(" "), boost::token_compress_on);
9352 std::swap(g_modsLoaded, mods);
9353
9354 // Abort init and restart
9355 RestartEngine();
9356 return false;
9357 }
9358 }
9359
9360 new L10n;
9361
9362 // Optionally start profiler HTTP output automatically
9363 // (By default it's only enabled by a hotkey, for security/performance)
9364 bool profilerHTTPEnable = false;
9365 CFG_GET_VAL("profiler2.autoenable", profilerHTTPEnable);
9366 if (profilerHTTPEnable)
9367 g_Profiler2.EnableHTTP();
9368
9369 // Initialise everything except Win32 sockets (because our networking
9370 // system already inits those)
9371 curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32);
9372
9373 if (!g_Quickstart)
9374 g_UserReporter.Initialize(); // after config
9375
9376 PROFILE2_EVENT("Init finished");
9377 return true;
9378 }
9379
9380 void InitGraphics(const CmdLineArgs& args, int flags, const std::vector<CStr>& installedMods)
9381 {
9382 const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0;
9383
9384 if(setup_vmode)
9385 {
9386 InitSDL();
9387
9388 if (!g_VideoMode.InitSDL())
9389 throw PSERROR_System_VmodeFailed(); // abort startup
9390 }
9391
9392 RunHardwareDetection();
9393
9394 const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file
9395 SetTextureQuality(quality);
9396
9397 ogl_WarnIfError();
9398
9399 // Optionally start profiler GPU timings automatically
9400 // (By default it's only enabled by a hotkey, for performance/compatibility)
9401 bool profilerGPUEnable = false;
9402 CFG_GET_VAL("profiler2.autoenable", profilerGPUEnable);
9403 if (profilerGPUEnable)
9404 g_Profiler2.EnableGPU();
9405
9406 if(!g_Quickstart)
9407 {
9408 WriteSystemInfo();
9409 // note: no longer vfs_display here. it's dog-slow due to unbuffered
9410 // file output and very rarely needed.
9411 }
9412
9413 if(g_DisableAudio)
9414 ISoundManager::SetEnabled(false);
9415
9416 g_GUI = new CGUIManager();
9417
9418 // (must come after SetVideoMode, since it calls ogl_Init)
9419 if (ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL) != 0 // ARB
9420 && ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", NULL) != 0) // GLSL
9421 {
9422 DEBUG_DISPLAY_ERROR(
9423 L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders."
9424 L" In the future, the game will not support pre-shader graphics cards."
9425 L" You are advised to try installing newer drivers and/or upgrade your graphics card."
9426 L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734"
9427 );
9428 // TODO: actually quit once fixed function support is dropped
9429 }
9430
9431 const char* missing = ogl_HaveExtensions(0,
9432 "GL_ARB_multitexture",
9433 "GL_EXT_draw_range_elements",
9434 "GL_ARB_texture_env_combine",
9435 "GL_ARB_texture_env_dot3",
9436 NULL);
9437 if(missing)
9438 {
9439 wchar_t buf[500];
9440 swprintf_s(buf, ARRAY_SIZE(buf),
9441 L"The %hs extension doesn't appear to be available on your computer."
9442 L" The game may still work, though - you are welcome to try at your own risk."
9443 L" If not or it doesn't look right, upgrade your graphics card.",
9444 missing
9445 );
9446 DEBUG_DISPLAY_ERROR(buf);
9447 // TODO: i18n
9448 }
9449
9450 if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar"))
9451 {
9452 DEBUG_DISPLAY_ERROR(
9453 L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer."
9454 L" Shadows are not available and overall graphics quality might suffer."
9455 L" You are advised to try installing newer drivers and/or upgrade your graphics card.");
9456 g_Shadows = false;
9457 }
9458
9459 ogl_WarnIfError();
9460 InitRenderer();
9461
9462 InitInput();
9463
9464 ogl_WarnIfError();
9465
9466 // TODO: Is this the best place for this?
9467 if (VfsDirectoryExists(L"maps/"))
9468 CXeromyces::AddValidator(g_VFS, "map", "maps/scenario.rng");
9469
9470 try
9471 {
9472 if (!AutostartVisualReplay(args.Get("replay-visual")) && !Autostart(args))
9473 {
9474 const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
9475 // We only want to display the splash screen at startup
9476 shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetScriptInterface();
9477 JSContext* cx = scriptInterface->GetContext();
9478 JSAutoRequest rq(cx);
9479 JS::RootedValue data(cx);
9480 if (g_GUI)
9481 {
9482 ScriptInterface::CreateObject(cx, &data, "isStartup", true);
9483 if (!installedMods.empty())
9484 scriptInterface->SetProperty(data, "installedMods", installedMods);
9485 }
9486 InitPs(setup_gui, installedMods.empty() ? L"page_pregame.xml" : L"page_modmod.xml", g_GUI->GetScriptInterface().get(), data);
9487 }
9488 }
9489 catch (PSERROR_Game_World_MapLoadFailed& e)
9490 {
9491 // Map Loading failed
9492
9493 // Start the engine so we have a GUI
9494 InitPs(true, L"page_pregame.xml", NULL, JS::UndefinedHandleValue);
9495
9496 // Call script function to do the actual work
9497 // (delete game data, switch GUI page, show error, etc.)
9498 CancelLoad(CStr(e.what()).FromUTF8());
9499 }
9500 }
9501
9502 void InitNonVisual(const CmdLineArgs& args)
9503 {
9504 // Need some stuff for terrain movement costs:
9505 // (TODO: this ought to be independent of any graphics code)
9506 new CTerrainTextureManager;
9507 g_TexMan.LoadTerrainTextures();
9508 Autostart(args);
9509 }
9510
9511 void RenderGui(bool RenderingState)
9512 {
9513 g_DoRenderGui = RenderingState;
9514 }
9515
9516 void RenderLogger(bool RenderingState)
9517 {
9518 g_DoRenderLogger = RenderingState;
9519 }
9520
9521 void RenderCursor(bool RenderingState)
9522 {
9523 g_DoRenderCursor = RenderingState;
9524 }
9525
9526 /**
9527 * Temporarily loads a scenario map and retrieves the "ScriptSettings" JSON
9528 * data from it.
9529 * The scenario map format is used for scenario and skirmish map types (random
9530 * games do not use a "map" (format) but a small JavaScript program which
9531 * creates a map on the fly). It contains a section to initialize the game
9532 * setup screen.
9533 * @param mapPath Absolute path (from VFS root) to the map file to peek in.
9534 * @return ScriptSettings in JSON format extracted from the map.
9535 */
9536 CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath)
9537 {
9538 CXeromyces mapFile;
9539 const char *pathToSettings[] =
9540 {
9541 "Scenario", "ScriptSettings", "" // Path to JSON data in map
9542 };
9543
9544 Status loadResult = mapFile.Load(g_VFS, mapPath);
9545
9546 if (INFO::OK != loadResult)
9547 {
9548 LOGERROR("LoadSettingsOfScenarioMap: Unable to load map file '%s'", mapPath.string8());
9549 throw PSERROR_Game_World_MapLoadFailed("Unable to load map file, check the path for typos.");
9550 }
9551 XMBElement mapElement = mapFile.GetRoot();
9552
9553 // Select the ScriptSettings node in the map file...
9554 for (int i = 0; pathToSettings[i][0]; ++i)
9555 {
9556 int childId = mapFile.GetElementID(pathToSettings[i]);
9557
9558 XMBElementList nodes = mapElement.GetChildNodes();
9559 auto it = std::find_if(nodes.begin(), nodes.end(), [&childId](const XMBElement& child) {
9560 return child.GetNodeName() == childId;
9561 });
9562
9563 if (it != nodes.end())
9564 mapElement = *it;
9565 }
9566 // ... they contain a JSON document to initialize the game setup
9567 // screen
9568 return mapElement.GetText();
9569 }
9570
9571 /*
9572 * Command line options for autostart
9573 * (keep synchronized with binaries/system/readme.txt):
9574 *
9575 * -autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME;
9576 * TYPEDIR is skirmishes, scenarios, or random
9577 * -autostart-seed=SEED sets randomization seed value (default 0, use -1 for random)
9578 * -autostart-ai=PLAYER:AI sets the AI for PLAYER (e.g. 2:petra)
9579 * -autostart-aidiff=PLAYER:DIFF sets the DIFFiculty of PLAYER's AI
9580 * (0: sandbox, 5: very hard)
9581 * -autostart-aiseed=AISEED sets the seed used for the AI random
9582 * generator (default 0, use -1 for random)
9583 * -autostart-player=NUMBER sets the playerID in non-networked games (default 1, use -1 for observer)
9584 * -autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV
9585 * (skirmish and random maps only)
9586 * -autostart-team=PLAYER:TEAM sets the team for PLAYER (e.g. 2:2).
9587 * -autostart-ceasefire=NUM sets a ceasefire duration NUM
9588 * (default 0 minutes)
9589 * -autostart-nonvisual disable any graphics and sounds
9590 * -autostart-victory=SCRIPTNAME sets the victory conditions with SCRIPTNAME
9591 * located in simulation/data/settings/victory_conditions/
9592 * (default conquest). When the first given SCRIPTNAME is
9593 * "endless", no victory conditions will apply.
9594 * -autostart-wonderduration=NUM sets the victory duration NUM for wonder victory condition
9595 * (default 10 minutes)
9596 * -autostart-relicduration=NUM sets the victory duration NUM for relic victory condition
9597 * (default 10 minutes)
9598 * -autostart-reliccount=NUM sets the number of relics for relic victory condition
9599 * (default 2 relics)
9600 * -autostart-disable-replay disable saving of replays
9601 *
9602 * Multiplayer:
9603 * -autostart-playername=NAME sets local player NAME (default 'anonymous')
9604 * -autostart-host sets multiplayer host mode
9605 * -autostart-host-players=NUMBER sets NUMBER of human players for multiplayer
9606 * game (default 2)
9607 * -autostart-client=IP sets multiplayer client to join host at
9608 * given IP address
9609 * Random maps only:
9610 * -autostart-size=TILES sets random map size in TILES (default 192)
9611 * -autostart-players=NUMBER sets NUMBER of players on random map
9612 * (default 2)
9613 *
9614 * Examples:
9615 * 1) "Bob" will host a 2 player game on the Arcadia map:
9616 * -autostart="scenarios/Arcadia" -autostart-host -autostart-host-players=2 -autostart-playername="Bob"
9617 * "Alice" joins the match as player 2:
9618 * -autostart="scenarios/Arcadia" -autostart-client=127.0.0.1 -autostart-playername="Alice"
9619 * The players use the developer overlay to control players.
9620 *
9621 * 2) Load Alpine Lakes random map with random seed, 2 players (Athens and Britons), and player 2 is PetraBot:
9622 * -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra
9623 *
9624 * 3) Observe the PetraBot on a triggerscript map:
9625 * -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
9626 */
9627 bool Autostart(const CmdLineArgs& args)
9628 {
9629 CStr autoStartName = args.Get("autostart");
9630
9631 if (autoStartName.empty())
9632 return false;
9633
9634 g_Game = new CGame(!args.Has("autostart-disable-replay"));
9635
9636 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
9637 JSContext* cx = scriptInterface.GetContext();
9638 JSAutoRequest rq(cx);
9639
9640 JS::RootedValue attrs(cx);
9641 JS::RootedValue settings(cx);
9642 JS::RootedValue playerData(cx);
9643
9644 ScriptInterface::CreateObject(cx, &attrs);
9645 ScriptInterface::CreateObject(cx, &settings);
9646 ScriptInterface::CreateArray(cx, &playerData);
9647
9648 // The directory in front of the actual map name indicates which type
9649 // of map is being loaded. Drawback of this approach is the association
9650 // of map types and folders is hard-coded, but benefits are:
9651 // - No need to pass the map type via command line separately
9652 // - Prevents mixing up of scenarios and skirmish maps to some degree
9653 Path mapPath = Path(autoStartName);
9654 std::wstring mapDirectory = mapPath.Parent().Filename().string();
9655 std::string mapType;
9656
9657 if (mapDirectory == L"random")
9658 {
9659 // Random map definition will be loaded from JSON file, so we need to parse it
9660 std::wstring scriptPath = L"maps/" + autoStartName.FromUTF8() + L".json";
9661 JS::RootedValue scriptData(cx);
9662 scriptInterface.ReadJSONFile(scriptPath, &scriptData);
9663 if (!scriptData.isUndefined() && scriptInterface.GetProperty(scriptData, "settings", &settings))
9664 {
9665 // JSON loaded ok - copy script name over to game attributes
9666 std::wstring scriptFile;
9667 scriptInterface.GetProperty(settings, "Script", scriptFile);
9668 scriptInterface.SetProperty(attrs, "script", scriptFile); // RMS filename
9669 }
9670 else
9671 {
9672 // Problem with JSON file
9673 LOGERROR("Autostart: Error reading random map script '%s'", utf8_from_wstring(scriptPath));
9674 throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details.");
9675 }
9676
9677 // Get optional map size argument (default 192)
9678 uint mapSize = 192;
9679 if (args.Has("autostart-size"))
9680 {
9681 CStr size = args.Get("autostart-size");
9682 mapSize = size.ToUInt();
9683 }
9684
9685 scriptInterface.SetProperty(settings, "Size", mapSize); // Random map size (in patches)
9686
9687 // Get optional number of players (default 2)
9688 size_t numPlayers = 2;
9689 if (args.Has("autostart-players"))
9690 {
9691 CStr num = args.Get("autostart-players");
9692 numPlayers = num.ToUInt();
9693 }
9694
9695 // Set up player data
9696 for (size_t i = 0; i < numPlayers; ++i)
9697 {
9698 JS::RootedValue player(cx);
9699
9700 // We could load player_defaults.json here, but that would complicate the logic
9701 // even more and autostart is only intended for developers anyway
9702 ScriptInterface::CreateObject(cx, &player, "Civ", "athen");
9703
9704 scriptInterface.SetPropertyInt(playerData, i, player);
9705 }
9706 mapType = "random";
9707 }
9708 else if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
9709 {
9710 // Initialize general settings from the map data so some values
9711 // (e.g. name of map) are always present, even when autostart is
9712 // partially configured
9713 CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml");
9714 scriptInterface.ParseJSON(mapSettingsJSON, &settings);
9715
9716 // Initialize the playerData array being modified by autostart
9717 // with the real map data, so sensible values are present:
9718 scriptInterface.GetProperty(settings, "PlayerData", &playerData);
9719
9720 if (mapDirectory == L"scenarios")
9721 mapType = "scenario";
9722 else
9723 mapType = "skirmish";
9724 }
9725 else
9726 {
9727 LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory));
9728 throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types.");
9729 }
9730
9731 scriptInterface.SetProperty(attrs, "mapType", mapType);
9732 scriptInterface.SetProperty(attrs, "map", "maps/" + autoStartName);
9733 scriptInterface.SetProperty(settings, "mapType", mapType);
9734 scriptInterface.SetProperty(settings, "CheatsEnabled", true);
9735
9736 // The seed is used for both random map generation and simulation
9737 u32 seed = 0;
9738 if (args.Has("autostart-seed"))
9739 {
9740 CStr seedArg = args.Get("autostart-seed");
9741 if (seedArg == "-1")
9742 seed = rand();
9743 else
9744 seed = seedArg.ToULong();
9745 }
9746 scriptInterface.SetProperty(settings, "Seed", seed);
9747
9748 // Set seed for AIs
9749 u32 aiseed = 0;
9750 if (args.Has("autostart-aiseed"))
9751 {
9752 CStr seedArg = args.Get("autostart-aiseed");
9753 if (seedArg == "-1")
9754 aiseed = rand();
9755 else
9756 aiseed = seedArg.ToULong();
9757 }
9758 scriptInterface.SetProperty(settings, "AISeed", aiseed);
9759
9760 // Set player data for AIs
9761 // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }
9762 // or = { PlayerData: [ null, { AI: ... }, ... ] } when gaia set
9763 int offset = 1;
9764 JS::RootedValue player(cx);
9765 if (scriptInterface.GetPropertyInt(playerData, 0, &player) && player.isNull())
9766 offset = 0;
9767
9768 // Set teams
9769 if (args.Has("autostart-team"))
9770 {
9771 std::vector<CStr> civArgs = args.GetMultiple("autostart-team");
9772 for (size_t i = 0; i < civArgs.size(); ++i)
9773 {
9774 int playerID = civArgs[i].BeforeFirst(":").ToInt();
9775
9776 // Instead of overwriting existing player data, modify the array
9777 JS::RootedValue player(cx);
9778 if (!scriptInterface.GetPropertyInt(playerData, playerID-offset, &player) || player.isUndefined())
9779 {
9780 if (mapDirectory == L"skirmishes")
9781 {
9782 // playerID is certainly bigger than this map player number
9783 LOGWARNING("Autostart: Invalid player %d in autostart-team option", playerID);
9784 continue;
9785 }
9786 ScriptInterface::CreateObject(cx, &player);
9787 }
9788
9789 int teamID = civArgs[i].AfterFirst(":").ToInt() - 1;
9790 scriptInterface.SetProperty(player, "Team", teamID);
9791 scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
9792 }
9793 }
9794
9795 int ceasefire = 0;
9796 if (args.Has("autostart-ceasefire"))
9797 ceasefire = args.Get("autostart-ceasefire").ToInt();
9798 scriptInterface.SetProperty(settings, "Ceasefire", ceasefire);
9799
9800 if (args.Has("autostart-ai"))
9801 {
9802 std::vector<CStr> aiArgs = args.GetMultiple("autostart-ai");
9803 for (size_t i = 0; i < aiArgs.size(); ++i)
9804 {
9805 int playerID = aiArgs[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-ai option", playerID);
9815 continue;
9816 }
9817 ScriptInterface::CreateObject(cx, &player);
9818 }
9819
9820 scriptInterface.SetProperty(player, "AI", aiArgs[i].AfterFirst(":"));
9821 scriptInterface.SetProperty(player, "AIDiff", 3);
9822 scriptInterface.SetProperty(player, "AIBehavior", "balanced");
9823 scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
9824 }
9825 }
9826 // Set AI difficulty
9827 if (args.Has("autostart-aidiff"))
9828 {
9829 std::vector<CStr> civArgs = args.GetMultiple("autostart-aidiff");
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"scenarios" || mapDirectory == L"skirmishes")
9839 {
9840 // playerID is certainly bigger than this map player number
9841 LOGWARNING("Autostart: Invalid player %d in autostart-aidiff option", playerID);
9842 continue;
9843 }
9844 ScriptInterface::CreateObject(cx, &player);
9845 }
9846
9847 scriptInterface.SetProperty(player, "AIDiff", civArgs[i].AfterFirst(":").ToInt());
9848 scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
9849 }
9850 }
9851 // Set player data for Civs
9852 if (args.Has("autostart-civ"))
9853 {
9854 if (mapDirectory != L"scenarios")
9855 {
9856 std::vector<CStr> civArgs = args.GetMultiple("autostart-civ");
9857 for (size_t i = 0; i < civArgs.size(); ++i)
9858 {
9859 int playerID = civArgs[i].BeforeFirst(":").ToInt();
9860
9861 // Instead of overwriting existing player data, modify the array
9862 JS::RootedValue player(cx);
9863 if (!scriptInterface.GetPropertyInt(playerData, playerID-offset, &player) || player.isUndefined())
9864 {
9865 if (mapDirectory == L"skirmishes")
9866 {
9867 // playerID is certainly bigger than this map player number
9868 LOGWARNING("Autostart: Invalid player %d in autostart-civ option", playerID);
9869 continue;
9870 }
9871 ScriptInterface::CreateObject(cx, &player);
9872 }
9873
9874 scriptInterface.SetProperty(player, "Civ", civArgs[i].AfterFirst(":"));
9875 scriptInterface.SetPropertyInt(playerData, playerID-offset, player);
9876 }
9877 }
9878 else
9879 LOGWARNING("Autostart: Option 'autostart-civ' is invalid for scenarios");
9880 }
9881
9882 // Add player data to map settings
9883 scriptInterface.SetProperty(settings, "PlayerData", playerData);
9884
9885 // Add map settings to game attributes
9886 scriptInterface.SetProperty(attrs, "settings", settings);
9887
9888 // Get optional playername
9889 CStrW userName = L"anonymous";
9890 if (args.Has("autostart-playername"))
9891 userName = args.Get("autostart-playername").FromUTF8();
9892
9893 // Add additional scripts to the TriggerScripts property
9894 std::vector<CStrW> triggerScriptsVector;
9895 JS::RootedValue triggerScripts(cx);
9896
9897 if (scriptInterface.HasProperty(settings, "TriggerScripts"))
9898 {
9899 scriptInterface.GetProperty(settings, "TriggerScripts", &triggerScripts);
9900 FromJSVal_vector(cx, triggerScripts, triggerScriptsVector);
9901 }
9902
9903 if (!CRenderer::IsInitialised())
9904 {
9905 CStr nonVisualScript = "scripts/NonVisualTrigger.js";
9906 triggerScriptsVector.push_back(nonVisualScript.FromUTF8());
9907 }
9908
9909 std::vector<CStr> victoryConditions(1, "conquest");
9910 if (args.Has("autostart-victory"))
9911 victoryConditions = args.GetMultiple("autostart-victory");
9912
9913 if (victoryConditions.size() == 1 && victoryConditions[0] == "endless")
9914 victoryConditions.clear();
9915
9916 scriptInterface.SetProperty(settings, "VictoryConditions", victoryConditions);
9917
9918 for (const CStr& victory : victoryConditions)
9919 {
9920 JS::RootedValue scriptData(cx);
9921 JS::RootedValue data(cx);
9922 JS::RootedValue victoryScripts(cx);
9923
9924 CStrW scriptPath = L"simulation/data/settings/victory_conditions/" + victory.FromUTF8() + L".json";
9925 scriptInterface.ReadJSONFile(scriptPath, &scriptData);
9926 if (!scriptData.isUndefined() && scriptInterface.GetProperty(scriptData, "Data", &data) && !data.isUndefined()
9927 && scriptInterface.GetProperty(data, "Scripts", &victoryScripts) && !victoryScripts.isUndefined())
9928 {
9929 std::vector<CStrW> victoryScriptsVector;
9930 FromJSVal_vector(cx, victoryScripts, victoryScriptsVector);
9931 triggerScriptsVector.insert(triggerScriptsVector.end(), victoryScriptsVector.begin(), victoryScriptsVector.end());
9932 }
9933 else
9934 {
9935 LOGERROR("Autostart: Error reading victory script '%s'", utf8_from_wstring(scriptPath));
9936 throw PSERROR_Game_World_MapLoadFailed("Error reading victory script.\nCheck application log for details.");
9937 }
9938 }
9939
9940 ToJSVal_vector(cx, &triggerScripts, triggerScriptsVector);
9941 scriptInterface.SetProperty(settings, "TriggerScripts", triggerScripts);
9942
9943 int wonderDuration = 10;
9944 if (args.Has("autostart-wonderduration"))
9945 wonderDuration = args.Get("autostart-wonderduration").ToInt();
9946 scriptInterface.SetProperty(settings, "WonderDuration", wonderDuration);
9947
9948 int relicDuration = 10;
9949 if (args.Has("autostart-relicduration"))
9950 relicDuration = args.Get("autostart-relicduration").ToInt();
9951 scriptInterface.SetProperty(settings, "RelicDuration", relicDuration);
9952
9953 int relicCount = 2;
9954 if (args.Has("autostart-reliccount"))
9955 relicCount = args.Get("autostart-reliccount").ToInt();
9956 scriptInterface.SetProperty(settings, "RelicCount", relicCount);
9957
9958 if (args.Has("autostart-host"))
9959 {
9960 InitPsAutostart(true, attrs);
9961
9962 size_t maxPlayers = 2;
9963 if (args.Has("autostart-host-players"))
9964 maxPlayers = args.Get("autostart-host-players").ToUInt();
9965
9966 g_NetServer = new CNetServer(false, maxPlayers);
9967
9968 g_NetServer->UpdateGameAttributes(&attrs, scriptInterface);
9969
9970 bool ok = g_NetServer->SetupConnection(PS_DEFAULT_PORT);
9971 ENSURE(ok);
9972
9973 g_NetClient = new CNetClient(g_Game, true);
9974 g_NetClient->SetUserName(userName);
9975 g_NetClient->SetupConnection("127.0.0.1", PS_DEFAULT_PORT, nullptr);
9976 }
9977 else if (args.Has("autostart-client"))
9978 {
9979 InitPsAutostart(true, attrs);
9980
9981 g_NetClient = new CNetClient(g_Game, false);
9982 g_NetClient->SetUserName(userName);
9983
9984 CStr ip = args.Get("autostart-client");
9985 if (ip.empty())
9986 ip = "127.0.0.1";
9987
9988 bool ok = g_NetClient->SetupConnection(ip, PS_DEFAULT_PORT, nullptr);
9989 ENSURE(ok);
9990 }
9991 else
9992 {
9993 g_Game->SetPlayerID(args.Has("autostart-player") ? args.Get("autostart-player").ToInt() : 1);
9994
9995 g_Game->StartGame(&attrs, "");
9996
9997 if (CRenderer::IsInitialised())
9998 {
9999 InitPsAutostart(false, attrs);
10000 }
10001 else
10002 {
10003 // TODO: Non progressive load can fail - need a decent way to handle this
10004 LDR_NonprogressiveLoad();
10005 ENSURE(g_Game->ReallyStartGame() == PSRETURN_OK);
10006 }
10007 }
10008
10009 return true;
10010 }
10011
10012 bool AutostartVisualReplay(const std::string& replayFile)
10013 {
10014 if (!FileExists(OsPath(replayFile)))
10015 return false;
10016
10017 g_Game = new CGame(false);
10018 g_Game->SetPlayerID(-1);
10019 g_Game->StartVisualReplay(replayFile);
10020
10021 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
10022 JSContext* cx = scriptInterface.GetContext();
10023 JSAutoRequest rq(cx);
10024 JS::RootedValue attrs(cx, g_Game->GetSimulation2()->GetInitAttributes());
10025
10026 InitPsAutostart(false, attrs);
10027
10028 return true;
10029 }
10030
10031 void CancelLoad(const CStrW& message)
10032 {
10033 shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
10034 JSContext* cx = pScriptInterface->GetContext();
10035 JSAutoRequest rq(cx);
10036
10037 JS::RootedValue global(cx, pScriptInterface->GetGlobalObject());
10038
10039 LDR_Cancel();
10040
10041 if (g_GUI &&
10042 g_GUI->GetPageCount() &&
10043 pScriptInterface->HasProperty(global, "cancelOnLoadGameError"))
10044 pScriptInterface->CallFunctionVoid(global, "cancelOnLoadGameError", message);
10045 }
10046
10047 bool InDevelopmentCopy()
10048 {
10049 if (!g_CheckedIfInDevelopmentCopy)
10050 {
10051 g_InDevelopmentCopy = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK);
10052 g_CheckedIfInDevelopmentCopy = true;
10053 }
10054 return g_InDevelopmentCopy;
10055 }
10056Index: source/ps/GameSetup/HWDetect.cpp
10057===================================================================
10058--- source/ps/GameSetup/HWDetect.cpp (revision 23275)
10059+++ source/ps/GameSetup/HWDetect.cpp (working copy)
10060@@ -1,778 +1,780 @@
10061 /* Copyright (C) 2019 Wildfire Games.
10062 * This file is part of 0 A.D.
10063 *
10064 * 0 A.D. is free software: you can redistribute it and/or modify
10065 * it under the terms of the GNU General Public License as published by
10066 * the Free Software Foundation, either version 2 of the License, or
10067 * (at your option) any later version.
10068 *
10069 * 0 A.D. is distributed in the hope that it will be useful,
10070 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10071 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10072 * GNU General Public License for more details.
10073 *
10074 * You should have received a copy of the GNU General Public License
10075 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
10076 */
10077
10078 #include "precompiled.h"
10079
10080 #include "scriptinterface/ScriptInterface.h"
10081
10082 #include "lib/ogl.h"
10083 #include "lib/snd.h"
10084 #include "lib/svn_revision.h"
10085 #include "lib/timer.h"
10086 #include "lib/utf8.h"
10087 #include "lib/external_libraries/libsdl.h"
10088 #include "lib/res/graphics/ogl_tex.h"
10089 #include "lib/posix/posix_utsname.h"
10090 #include "lib/sysdep/cpu.h"
10091 #include "lib/sysdep/gfx.h"
10092 #include "lib/sysdep/numa.h"
10093 #include "lib/sysdep/os_cpu.h"
10094 #if ARCH_X86_X64
10095 # include "lib/sysdep/arch/x86_x64/cache.h"
10096 # include "lib/sysdep/arch/x86_x64/topology.h"
10097 #endif
10098 #include "ps/CLogger.h"
10099 #include "ps/ConfigDB.h"
10100 #include "ps/Filesystem.h"
10101 #include "ps/GameSetup/Config.h"
10102 #include "ps/Profile.h"
10103 #include "ps/scripting/JSInterface_Debug.h"
10104 #include "ps/UserReport.h"
10105 #include "ps/VideoMode.h"
10106
10107 #ifdef SDL_VIDEO_DRIVER_X11
10108 #include <GL/glx.h>
10109 #include "SDL_syswm.h"
10110
10111 // Define the GLX_MESA_query_renderer macros if built with
10112 // an old Mesa (<10.0) that doesn't provide them
10113 #ifndef GLX_MESA_query_renderer
10114 #define GLX_MESA_query_renderer 1
10115 #define GLX_RENDERER_VENDOR_ID_MESA 0x8183
10116 #define GLX_RENDERER_DEVICE_ID_MESA 0x8184
10117 #define GLX_RENDERER_VERSION_MESA 0x8185
10118 #define GLX_RENDERER_ACCELERATED_MESA 0x8186
10119 #define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187
10120 #define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA 0x8188
10121 #define GLX_RENDERER_PREFERRED_PROFILE_MESA 0x8189
10122 #define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA 0x818A
10123 #define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B
10124 #define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA 0x818C
10125 #define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA 0x818D
10126 #define GLX_RENDERER_ID_MESA 0x818E
10127 #endif /* GLX_MESA_query_renderer */
10128
10129 #endif
10130
10131 static void ReportGLLimits(const ScriptInterface& scriptInterface, JS::HandleValue settings);
10132
10133 #if ARCH_X86_X64
10134 void ConvertCaches(const ScriptInterface& scriptInterface, x86_x64::IdxCache idxCache, JS::MutableHandleValue ret)
10135 {
10136 JSContext* cx = scriptInterface.GetContext();
10137 JSAutoRequest rq(cx);
10138
10139 ScriptInterface::CreateArray(cx, ret);
10140
10141 for (size_t idxLevel = 0; idxLevel < x86_x64::Cache::maxLevels; ++idxLevel)
10142 {
10143 const x86_x64::Cache* pcache = x86_x64::Caches(idxCache+idxLevel);
10144 if (pcache->m_Type == x86_x64::Cache::kNull || pcache->m_NumEntries == 0)
10145 continue;
10146
10147 JS::RootedValue cache(cx);
10148
10149 ScriptInterface::CreateObject(
10150 cx,
10151 &cache,
10152 "type", static_cast<u32>(pcache->m_Type),
10153 "level", static_cast<u32>(pcache->m_Level),
10154 "associativity", static_cast<u32>(pcache->m_Associativity),
10155 "linesize", static_cast<u32>(pcache->m_EntrySize),
10156 "sharedby", static_cast<u32>(pcache->m_SharedBy),
10157 "totalsize", static_cast<u32>(pcache->TotalSize()));
10158
10159 scriptInterface.SetPropertyInt(ret, idxLevel, cache);
10160 }
10161 }
10162
10163 void ConvertTLBs(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
10164 {
10165 JSContext* cx = scriptInterface.GetContext();
10166 JSAutoRequest rq(cx);
10167
10168 ScriptInterface::CreateArray(cx, ret);
10169
10170 for(size_t i = 0; ; i++)
10171 {
10172 const x86_x64::Cache* ptlb = x86_x64::Caches(x86_x64::TLB+i);
10173 if (!ptlb)
10174 break;
10175
10176 JS::RootedValue tlb(cx);
10177
10178 ScriptInterface::CreateObject(
10179 cx,
10180 &tlb,
10181 "type", static_cast<u32>(ptlb->m_Type),
10182 "level", static_cast<u32>(ptlb->m_Level),
10183 "associativity", static_cast<u32>(ptlb->m_Associativity),
10184 "pagesize", static_cast<u32>(ptlb->m_EntrySize),
10185 "entries", static_cast<u32>(ptlb->m_NumEntries));
10186
10187 scriptInterface.SetPropertyInt(ret, i, tlb);
10188 }
10189 }
10190 #endif
10191
10192 // The Set* functions will override the default behaviour, unless the user
10193 // has explicitly set a config variable to override that.
10194 // (TODO: This is an ugly abuse of the config system)
10195 static bool IsOverridden(const char* setting)
10196 {
10197 EConfigNamespace ns = g_ConfigDB.GetValueNamespace(CFG_COMMAND, setting);
10198 return !(ns == CFG_LAST || ns == CFG_DEFAULT);
10199 }
10200
10201 void SetDisableAudio(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10202 {
10203 g_DisableAudio = disabled;
10204 }
10205
10206 void SetDisableS3TC(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10207 {
10208 if (!IsOverridden("nos3tc"))
10209 ogl_tex_override(OGL_TEX_S3TC, disabled ? OGL_TEX_DISABLE : OGL_TEX_ENABLE);
10210 }
10211
10212 void SetDisableShadows(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10213 {
10214 if (!IsOverridden("shadows"))
10215 g_Shadows = !disabled;
10216 }
10217
10218 void SetDisableShadowPCF(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10219 {
10220 if (!IsOverridden("shadowpcf"))
10221 g_ShadowPCF = !disabled;
10222 }
10223
10224 void SetDisableAllWater(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10225 {
10226 if (!IsOverridden("watereffects"))
10227 g_WaterEffects = !disabled;
10228 if (!IsOverridden("waterfancyeffects"))
10229 g_WaterFancyEffects = !disabled;
10230 if (!IsOverridden("waterrealdepth"))
10231 g_WaterRealDepth = !disabled;
10232 if (!IsOverridden("waterrefraction"))
10233 g_WaterRefraction = !disabled;
10234 if (!IsOverridden("waterreflection"))
10235 g_WaterReflection = !disabled;
10236 if (!IsOverridden("watershadows"))
10237 g_WaterShadows = !disabled;
10238 }
10239
10240 void SetDisableFancyWater(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool disabled)
10241 {
10242 if (!IsOverridden("waterfancyeffects"))
10243 g_WaterFancyEffects = !disabled;
10244 if (!IsOverridden("waterrealdepth"))
10245 g_WaterRealDepth = !disabled;
10246 if (!IsOverridden("watershadows"))
10247 g_WaterShadows = !disabled;
10248 }
10249
10250 void SetEnableGLSL(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
10251 {
10252 if (!IsOverridden("preferglsl"))
10253 g_PreferGLSL = enabled;
10254 }
10255
10256 void SetEnablePostProc(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
10257 {
10258 if (!IsOverridden("postproc"))
10259 g_PostProc = enabled;
10260 }
10261
10262 void SetEnableSmoothLOS(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
10263 {
10264 if (!IsOverridden("smoothlos"))
10265 g_SmoothLOS = enabled;
10266 }
10267
10268 void SetRenderPath(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& renderpath)
10269 {
10270 g_RenderPath = renderpath;
10271 }
10272
10273 void RunHardwareDetection()
10274 {
10275 TIMER(L"RunHardwareDetection");
10276
10277 ScriptInterface scriptInterface("Engine", "HWDetect", g_ScriptRuntime);
10278 JSContext* cx = scriptInterface.GetContext();
10279 JSAutoRequest rq(cx);
10280
10281 JSI_Debug::RegisterScriptFunctions(scriptInterface); // Engine.DisplayErrorDialog
10282
10283 scriptInterface.RegisterFunction<void, bool, &SetDisableAudio>("SetDisableAudio");
10284 scriptInterface.RegisterFunction<void, bool, &SetDisableS3TC>("SetDisableS3TC");
10285 scriptInterface.RegisterFunction<void, bool, &SetDisableShadows>("SetDisableShadows");
10286 scriptInterface.RegisterFunction<void, bool, &SetDisableShadowPCF>("SetDisableShadowPCF");
10287 scriptInterface.RegisterFunction<void, bool, &SetDisableAllWater>("SetDisableAllWater");
10288 scriptInterface.RegisterFunction<void, bool, &SetDisableFancyWater>("SetDisableFancyWater");
10289 scriptInterface.RegisterFunction<void, bool, &SetEnableGLSL>("SetEnableGLSL");
10290 scriptInterface.RegisterFunction<void, bool, &SetEnablePostProc>("SetEnablePostProc");
10291 scriptInterface.RegisterFunction<void, bool, &SetEnableSmoothLOS>("SetEnableSmoothLOS");
10292 scriptInterface.RegisterFunction<void, std::string, &SetRenderPath>("SetRenderPath");
10293
10294 // Load the detection script:
10295
10296 const wchar_t* scriptName = L"hwdetect/hwdetect.js";
10297 CVFSFile file;
10298 if (file.Load(g_VFS, scriptName) != PSRETURN_OK)
10299 {
10300 LOGERROR("Failed to load hardware detection script");
10301 return;
10302 }
10303
10304 std::string code = file.DecodeUTF8(); // assume it's UTF-8
10305 scriptInterface.LoadScript(scriptName, code);
10306
10307 // Collect all the settings we'll pass to the script:
10308 // (We'll use this same data for the opt-in online reporting system, so it
10309 // includes some fields that aren't directly useful for the hwdetect script)
10310
10311 JS::RootedValue settings(cx);
10312 ScriptInterface::CreateObject(cx, &settings);
10313
10314 scriptInterface.SetProperty(settings, "os_unix", OS_UNIX);
10315 scriptInterface.SetProperty(settings, "os_bsd", OS_BSD);
10316 scriptInterface.SetProperty(settings, "os_linux", OS_LINUX);
10317 scriptInterface.SetProperty(settings, "os_android", OS_ANDROID);
10318 scriptInterface.SetProperty(settings, "os_macosx", OS_MACOSX);
10319 scriptInterface.SetProperty(settings, "os_win", OS_WIN);
10320
10321 scriptInterface.SetProperty(settings, "arch_ia32", ARCH_IA32);
10322 scriptInterface.SetProperty(settings, "arch_amd64", ARCH_AMD64);
10323 scriptInterface.SetProperty(settings, "arch_arm", ARCH_ARM);
10324 scriptInterface.SetProperty(settings, "arch_aarch64", ARCH_AARCH64);
10325
10326 #ifdef NDEBUG
10327 scriptInterface.SetProperty(settings, "build_debug", 0);
10328 #else
10329 scriptInterface.SetProperty(settings, "build_debug", 1);
10330 #endif
10331 scriptInterface.SetProperty(settings, "build_opengles", CONFIG2_GLES);
10332
10333 scriptInterface.SetProperty(settings, "build_datetime", std::string(__DATE__ " " __TIME__));
10334 scriptInterface.SetProperty(settings, "build_revision", std::wstring(svn_revision));
10335
10336 scriptInterface.SetProperty(settings, "build_msc", (int)MSC_VERSION);
10337 scriptInterface.SetProperty(settings, "build_icc", (int)ICC_VERSION);
10338 scriptInterface.SetProperty(settings, "build_gcc", (int)GCC_VERSION);
10339 scriptInterface.SetProperty(settings, "build_clang", (int)CLANG_VERSION);
10340
10341 scriptInterface.SetProperty(settings, "gfx_card", gfx::CardName());
10342 scriptInterface.SetProperty(settings, "gfx_drv_ver", gfx::DriverInfo());
10343
10344+#if CONFIG2_AUDIO
10345 scriptInterface.SetProperty(settings, "snd_card", snd_card);
10346 scriptInterface.SetProperty(settings, "snd_drv_ver", snd_drv_ver);
10347+#endif
10348
10349 ReportGLLimits(scriptInterface, settings);
10350
10351 scriptInterface.SetProperty(settings, "video_desktop_xres", g_VideoMode.GetDesktopXRes());
10352 scriptInterface.SetProperty(settings, "video_desktop_yres", g_VideoMode.GetDesktopYRes());
10353 scriptInterface.SetProperty(settings, "video_desktop_bpp", g_VideoMode.GetDesktopBPP());
10354 scriptInterface.SetProperty(settings, "video_desktop_freq", g_VideoMode.GetDesktopFreq());
10355
10356 struct utsname un;
10357 uname(&un);
10358 scriptInterface.SetProperty(settings, "uname_sysname", std::string(un.sysname));
10359 scriptInterface.SetProperty(settings, "uname_release", std::string(un.release));
10360 scriptInterface.SetProperty(settings, "uname_version", std::string(un.version));
10361 scriptInterface.SetProperty(settings, "uname_machine", std::string(un.machine));
10362
10363 #if OS_LINUX
10364 {
10365 std::ifstream ifs("/etc/lsb-release");
10366 if (ifs.good())
10367 {
10368 std::string str((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
10369 scriptInterface.SetProperty(settings, "linux_release", str);
10370 }
10371 }
10372 #endif
10373
10374 scriptInterface.SetProperty(settings, "cpu_identifier", std::string(cpu_IdentifierString()));
10375 scriptInterface.SetProperty(settings, "cpu_frequency", os_cpu_ClockFrequency());
10376 scriptInterface.SetProperty(settings, "cpu_pagesize", (u32)os_cpu_PageSize());
10377 scriptInterface.SetProperty(settings, "cpu_largepagesize", (u32)os_cpu_LargePageSize());
10378 scriptInterface.SetProperty(settings, "cpu_numprocs", (u32)os_cpu_NumProcessors());
10379 #if ARCH_X86_X64
10380 scriptInterface.SetProperty(settings, "cpu_numpackages", (u32)topology::NumPackages());
10381 scriptInterface.SetProperty(settings, "cpu_coresperpackage", (u32)topology::CoresPerPackage());
10382 scriptInterface.SetProperty(settings, "cpu_logicalpercore", (u32)topology::LogicalPerCore());
10383 scriptInterface.SetProperty(settings, "cpu_numcaches", (u32)topology::NumCaches());
10384 #endif
10385
10386 scriptInterface.SetProperty(settings, "numa_numnodes", (u32)numa_NumNodes());
10387 scriptInterface.SetProperty(settings, "numa_factor", numa_Factor());
10388 scriptInterface.SetProperty(settings, "numa_interleaved", numa_IsMemoryInterleaved());
10389
10390 scriptInterface.SetProperty(settings, "ram_total", (u32)os_cpu_MemorySize());
10391 scriptInterface.SetProperty(settings, "ram_total_os", (u32)os_cpu_QueryMemorySize());
10392
10393 #if ARCH_X86_X64
10394 scriptInterface.SetProperty(settings, "x86_vendor", (u32)x86_x64::Vendor());
10395 scriptInterface.SetProperty(settings, "x86_model", (u32)x86_x64::Model());
10396 scriptInterface.SetProperty(settings, "x86_family", (u32)x86_x64::Family());
10397
10398 u32 caps0, caps1, caps2, caps3;
10399 x86_x64::GetCapBits(&caps0, &caps1, &caps2, &caps3);
10400 scriptInterface.SetProperty(settings, "x86_caps[0]", caps0);
10401 scriptInterface.SetProperty(settings, "x86_caps[1]", caps1);
10402 scriptInterface.SetProperty(settings, "x86_caps[2]", caps2);
10403 scriptInterface.SetProperty(settings, "x86_caps[3]", caps3);
10404
10405 JS::RootedValue tmpVal(cx);
10406 ConvertCaches(scriptInterface, x86_x64::L1I, &tmpVal);
10407 scriptInterface.SetProperty(settings, "x86_icaches", tmpVal);
10408 ConvertCaches(scriptInterface, x86_x64::L1D, &tmpVal);
10409 scriptInterface.SetProperty(settings, "x86_dcaches", tmpVal);
10410 ConvertTLBs(scriptInterface, &tmpVal);
10411 scriptInterface.SetProperty(settings, "x86_tlbs", tmpVal);
10412 #endif
10413
10414 scriptInterface.SetProperty(settings, "timer_resolution", timer_Resolution());
10415
10416 // The version should be increased for every meaningful change.
10417 const int reportVersion = 12;
10418
10419 // Send the same data to the reporting system
10420 g_UserReporter.SubmitReport(
10421 "hwdetect",
10422 reportVersion,
10423 scriptInterface.StringifyJSON(&settings, false),
10424 scriptInterface.StringifyJSON(&settings, true));
10425
10426 // Run the detection script:
10427 JS::RootedValue global(cx, scriptInterface.GetGlobalObject());
10428 scriptInterface.CallFunctionVoid(global, "RunHardwareDetection", settings);
10429 }
10430
10431 static void ReportGLLimits(const ScriptInterface& scriptInterface, JS::HandleValue settings)
10432 {
10433 const char* errstr = "(error)";
10434
10435 #define INTEGER(id) do { \
10436 GLint i = -1; \
10437 glGetIntegerv(GL_##id, &i); \
10438 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10439 scriptInterface.SetProperty(settings, "GL_" #id, errstr); \
10440 else \
10441 scriptInterface.SetProperty(settings, "GL_" #id, i); \
10442 } while (false)
10443
10444 #define INTEGER2(id) do { \
10445 GLint i[2] = { -1, -1 }; \
10446 glGetIntegerv(GL_##id, i); \
10447 if (ogl_SquelchError(GL_INVALID_ENUM)) { \
10448 scriptInterface.SetProperty(settings, "GL_" #id "[0]", errstr); \
10449 scriptInterface.SetProperty(settings, "GL_" #id "[1]", errstr); \
10450 } else { \
10451 scriptInterface.SetProperty(settings, "GL_" #id "[0]", i[0]); \
10452 scriptInterface.SetProperty(settings, "GL_" #id "[1]", i[1]); \
10453 } \
10454 } while (false)
10455
10456 #define FLOAT(id) do { \
10457 GLfloat f = std::numeric_limits<GLfloat>::quiet_NaN(); \
10458 glGetFloatv(GL_##id, &f); \
10459 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10460 scriptInterface.SetProperty(settings, "GL_" #id, errstr); \
10461 else \
10462 scriptInterface.SetProperty(settings, "GL_" #id, f); \
10463 } while (false)
10464
10465 #define FLOAT2(id) do { \
10466 GLfloat f[2] = { std::numeric_limits<GLfloat>::quiet_NaN(), std::numeric_limits<GLfloat>::quiet_NaN() }; \
10467 glGetFloatv(GL_##id, f); \
10468 if (ogl_SquelchError(GL_INVALID_ENUM)) { \
10469 scriptInterface.SetProperty(settings, "GL_" #id "[0]", errstr); \
10470 scriptInterface.SetProperty(settings, "GL_" #id "[1]", errstr); \
10471 } else { \
10472 scriptInterface.SetProperty(settings, "GL_" #id "[0]", f[0]); \
10473 scriptInterface.SetProperty(settings, "GL_" #id "[1]", f[1]); \
10474 } \
10475 } while (false)
10476
10477 #define STRING(id) do { \
10478 const char* c = (const char*)glGetString(GL_##id); \
10479 if (!c) c = ""; \
10480 if (ogl_SquelchError(GL_INVALID_ENUM)) c = errstr; \
10481 scriptInterface.SetProperty(settings, "GL_" #id, std::string(c)); \
10482 } while (false)
10483
10484 #define QUERY(target, pname) do { \
10485 GLint i = -1; \
10486 pglGetQueryivARB(GL_##target, GL_##pname, &i); \
10487 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10488 scriptInterface.SetProperty(settings, "GL_" #target ".GL_" #pname, errstr); \
10489 else \
10490 scriptInterface.SetProperty(settings, "GL_" #target ".GL_" #pname, i); \
10491 } while (false)
10492
10493 #define VERTEXPROGRAM(id) do { \
10494 GLint i = -1; \
10495 pglGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_##id, &i); \
10496 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10497 scriptInterface.SetProperty(settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, errstr); \
10498 else \
10499 scriptInterface.SetProperty(settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, i); \
10500 } while (false)
10501
10502 #define FRAGMENTPROGRAM(id) do { \
10503 GLint i = -1; \
10504 pglGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_##id, &i); \
10505 if (ogl_SquelchError(GL_INVALID_ENUM)) \
10506 scriptInterface.SetProperty(settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, errstr); \
10507 else \
10508 scriptInterface.SetProperty(settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, i); \
10509 } while (false)
10510
10511 #define BOOL(id) INTEGER(id)
10512
10513 ogl_WarnIfError();
10514
10515 // Core OpenGL 1.3:
10516 // (We don't bother checking extension strings for anything older than 1.3;
10517 // it'll just produce harmless warnings)
10518 STRING(VERSION);
10519 STRING(VENDOR);
10520 STRING(RENDERER);
10521 STRING(EXTENSIONS);
10522 #if !CONFIG2_GLES
10523 INTEGER(MAX_LIGHTS);
10524 INTEGER(MAX_CLIP_PLANES);
10525 // Skip MAX_COLOR_MATRIX_STACK_DEPTH (only in imaging subset)
10526 INTEGER(MAX_MODELVIEW_STACK_DEPTH);
10527 INTEGER(MAX_PROJECTION_STACK_DEPTH);
10528 INTEGER(MAX_TEXTURE_STACK_DEPTH);
10529 #endif
10530 INTEGER(SUBPIXEL_BITS);
10531 #if !CONFIG2_GLES
10532 INTEGER(MAX_3D_TEXTURE_SIZE);
10533 #endif
10534 INTEGER(MAX_TEXTURE_SIZE);
10535 INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE);
10536 #if !CONFIG2_GLES
10537 INTEGER(MAX_PIXEL_MAP_TABLE);
10538 INTEGER(MAX_NAME_STACK_DEPTH);
10539 INTEGER(MAX_LIST_NESTING);
10540 INTEGER(MAX_EVAL_ORDER);
10541 #endif
10542 INTEGER2(MAX_VIEWPORT_DIMS);
10543 #if !CONFIG2_GLES
10544 INTEGER(MAX_ATTRIB_STACK_DEPTH);
10545 INTEGER(MAX_CLIENT_ATTRIB_STACK_DEPTH);
10546 INTEGER(AUX_BUFFERS);
10547 BOOL(RGBA_MODE);
10548 BOOL(INDEX_MODE);
10549 BOOL(DOUBLEBUFFER);
10550 BOOL(STEREO);
10551 #endif
10552 FLOAT2(ALIASED_POINT_SIZE_RANGE);
10553 #if !CONFIG2_GLES
10554 FLOAT2(SMOOTH_POINT_SIZE_RANGE);
10555 FLOAT(SMOOTH_POINT_SIZE_GRANULARITY);
10556 #endif
10557 FLOAT2(ALIASED_LINE_WIDTH_RANGE);
10558 #if !CONFIG2_GLES
10559 FLOAT2(SMOOTH_LINE_WIDTH_RANGE);
10560 FLOAT(SMOOTH_LINE_WIDTH_GRANULARITY);
10561 // Skip MAX_CONVOLUTION_WIDTH, MAX_CONVOLUTION_HEIGHT (only in imaging subset)
10562 INTEGER(MAX_ELEMENTS_INDICES);
10563 INTEGER(MAX_ELEMENTS_VERTICES);
10564 INTEGER(MAX_TEXTURE_UNITS);
10565 #endif
10566 INTEGER(SAMPLE_BUFFERS);
10567 INTEGER(SAMPLES);
10568 // TODO: compressed texture formats
10569 INTEGER(RED_BITS);
10570 INTEGER(GREEN_BITS);
10571 INTEGER(BLUE_BITS);
10572 INTEGER(ALPHA_BITS);
10573 #if !CONFIG2_GLES
10574 INTEGER(INDEX_BITS);
10575 #endif
10576 INTEGER(DEPTH_BITS);
10577 INTEGER(STENCIL_BITS);
10578 #if !CONFIG2_GLES
10579 INTEGER(ACCUM_RED_BITS);
10580 INTEGER(ACCUM_GREEN_BITS);
10581 INTEGER(ACCUM_BLUE_BITS);
10582 INTEGER(ACCUM_ALPHA_BITS);
10583 #endif
10584
10585 #if !CONFIG2_GLES
10586
10587 // Core OpenGL 2.0 (treated as extensions):
10588
10589 if (ogl_HaveExtension("GL_EXT_texture_lod_bias"))
10590 {
10591 FLOAT(MAX_TEXTURE_LOD_BIAS_EXT);
10592 }
10593
10594 if (ogl_HaveExtension("GL_ARB_occlusion_query"))
10595 {
10596 QUERY(SAMPLES_PASSED, QUERY_COUNTER_BITS);
10597 }
10598
10599 if (ogl_HaveExtension("GL_ARB_shading_language_100"))
10600 {
10601 STRING(SHADING_LANGUAGE_VERSION_ARB);
10602 }
10603
10604 if (ogl_HaveExtension("GL_ARB_vertex_shader"))
10605 {
10606 INTEGER(MAX_VERTEX_ATTRIBS_ARB);
10607 INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB);
10608 INTEGER(MAX_VARYING_FLOATS_ARB);
10609 INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB);
10610 INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB);
10611 }
10612
10613 if (ogl_HaveExtension("GL_ARB_fragment_shader"))
10614 {
10615 INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB);
10616 }
10617
10618 if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader") ||
10619 ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
10620 {
10621 INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB);
10622 INTEGER(MAX_TEXTURE_COORDS_ARB);
10623 }
10624
10625 if (ogl_HaveExtension("GL_ARB_draw_buffers"))
10626 {
10627 INTEGER(MAX_DRAW_BUFFERS_ARB);
10628 }
10629
10630 // Core OpenGL 3.0:
10631
10632 if (ogl_HaveExtension("GL_EXT_gpu_shader4"))
10633 {
10634 INTEGER(MIN_PROGRAM_TEXEL_OFFSET); // no _EXT version of these in glext.h
10635 INTEGER(MAX_PROGRAM_TEXEL_OFFSET);
10636 }
10637
10638 if (ogl_HaveExtension("GL_EXT_framebuffer_object"))
10639 {
10640 INTEGER(MAX_COLOR_ATTACHMENTS_EXT);
10641 INTEGER(MAX_RENDERBUFFER_SIZE_EXT);
10642 }
10643
10644 if (ogl_HaveExtension("GL_EXT_framebuffer_multisample"))
10645 {
10646 INTEGER(MAX_SAMPLES_EXT);
10647 }
10648
10649 if (ogl_HaveExtension("GL_EXT_texture_array"))
10650 {
10651 INTEGER(MAX_ARRAY_TEXTURE_LAYERS_EXT);
10652 }
10653
10654 if (ogl_HaveExtension("GL_EXT_transform_feedback"))
10655 {
10656 INTEGER(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT);
10657 INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT);
10658 INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT);
10659 }
10660
10661
10662 // Other interesting extensions:
10663
10664 if (ogl_HaveExtension("GL_EXT_timer_query") || ogl_HaveExtension("GL_ARB_timer_query"))
10665 {
10666 QUERY(TIME_ELAPSED, QUERY_COUNTER_BITS);
10667 }
10668
10669 if (ogl_HaveExtension("GL_ARB_timer_query"))
10670 {
10671 QUERY(TIMESTAMP, QUERY_COUNTER_BITS);
10672 }
10673
10674 if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"))
10675 {
10676 FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT);
10677 }
10678
10679 if (ogl_HaveExtension("GL_ARB_texture_rectangle"))
10680 {
10681 INTEGER(MAX_RECTANGLE_TEXTURE_SIZE_ARB);
10682 }
10683
10684 if (ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
10685 {
10686 INTEGER(MAX_PROGRAM_MATRICES_ARB);
10687 INTEGER(MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB);
10688 }
10689
10690 if (ogl_HaveExtension("GL_ARB_vertex_program"))
10691 {
10692 VERTEXPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
10693 VERTEXPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
10694 VERTEXPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
10695 VERTEXPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
10696 VERTEXPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
10697 VERTEXPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
10698 VERTEXPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
10699 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
10700 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
10701 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
10702 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
10703 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
10704
10705 if (ogl_HaveExtension("GL_ARB_fragment_program"))
10706 {
10707 // The spec seems to say these should be supported, but
10708 // Mesa complains about them so let's not bother
10709 /*
10710 VERTEXPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
10711 VERTEXPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
10712 VERTEXPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
10713 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
10714 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
10715 VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
10716 */
10717 }
10718 }
10719
10720 if (ogl_HaveExtension("GL_ARB_fragment_program"))
10721 {
10722 FRAGMENTPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
10723 FRAGMENTPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
10724 FRAGMENTPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
10725 FRAGMENTPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
10726 FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
10727 FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
10728 FRAGMENTPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
10729 FRAGMENTPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
10730 FRAGMENTPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
10731 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
10732 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
10733 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
10734 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
10735 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
10736 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
10737 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
10738
10739 if (ogl_HaveExtension("GL_ARB_vertex_program"))
10740 {
10741 // The spec seems to say these should be supported, but
10742 // Intel drivers on Windows complain about them so let's not bother
10743 /*
10744 FRAGMENTPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
10745 FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
10746 */
10747 }
10748 }
10749
10750 if (ogl_HaveExtension("GL_ARB_geometry_shader4"))
10751 {
10752 INTEGER(MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB);
10753 INTEGER(MAX_GEOMETRY_OUTPUT_VERTICES_ARB);
10754 INTEGER(MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB);
10755 INTEGER(MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB);
10756 INTEGER(MAX_GEOMETRY_VARYING_COMPONENTS_ARB);
10757 INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB);
10758 }
10759
10760 #else // CONFIG2_GLES
10761
10762 // Core OpenGL ES 2.0:
10763
10764 STRING(SHADING_LANGUAGE_VERSION);
10765 INTEGER(MAX_VERTEX_ATTRIBS);
10766 INTEGER(MAX_VERTEX_UNIFORM_VECTORS);
10767 INTEGER(MAX_VARYING_VECTORS);
10768 INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
10769 INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS);
10770 INTEGER(MAX_FRAGMENT_UNIFORM_VECTORS);
10771 INTEGER(MAX_TEXTURE_IMAGE_UNITS);
10772 INTEGER(MAX_RENDERBUFFER_SIZE);
10773
10774 #endif // CONFIG2_GLES
10775
10776
10777 #ifdef SDL_VIDEO_DRIVER_X11
10778
10779 #define GLXQCR_INTEGER(id) do { \
10780 unsigned int i = UINT_MAX; \
10781 if (pglXQueryCurrentRendererIntegerMESA(id, &i)) \
10782 scriptInterface.SetProperty(settings, #id, i); \
10783 } while (false)
10784
10785 #define GLXQCR_INTEGER2(id) do { \
10786 unsigned int i[2] = { UINT_MAX, UINT_MAX }; \
10787 if (pglXQueryCurrentRendererIntegerMESA(id, i)) { \
10788 scriptInterface.SetProperty(settings, #id "[0]", i[0]); \
10789 scriptInterface.SetProperty(settings, #id "[1]", i[1]); \
10790 } \
10791 } while (false)
10792
10793 #define GLXQCR_INTEGER3(id) do { \
10794 unsigned int i[3] = { UINT_MAX, UINT_MAX, UINT_MAX }; \
10795 if (pglXQueryCurrentRendererIntegerMESA(id, i)) { \
10796 scriptInterface.SetProperty(settings, #id "[0]", i[0]); \
10797 scriptInterface.SetProperty(settings, #id "[1]", i[1]); \
10798 scriptInterface.SetProperty(settings, #id "[2]", i[2]); \
10799 } \
10800 } while (false)
10801
10802 #define GLXQCR_STRING(id) do { \
10803 const char* str = pglXQueryCurrentRendererStringMESA(id); \
10804 if (str) \
10805 scriptInterface.SetProperty(settings, #id ".string", str); \
10806 } while (false)
10807
10808
10809 SDL_SysWMinfo wminfo;
10810 SDL_VERSION(&wminfo.version);
10811 const int ret = SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &wminfo);
10812 if (ret && wminfo.subsystem == SDL_SYSWM_X11)
10813 {
10814 Display* dpy = wminfo.info.x11.display;
10815 int scrnum = DefaultScreen(dpy);
10816
10817 const char* glxexts = glXQueryExtensionsString(dpy, scrnum);
10818
10819 scriptInterface.SetProperty(settings, "glx_extensions", glxexts);
10820
10821 if (strstr(glxexts, "GLX_MESA_query_renderer") && pglXQueryCurrentRendererIntegerMESA && pglXQueryCurrentRendererStringMESA)
10822 {
10823 GLXQCR_INTEGER(GLX_RENDERER_VENDOR_ID_MESA);
10824 GLXQCR_INTEGER(GLX_RENDERER_DEVICE_ID_MESA);
10825 GLXQCR_INTEGER3(GLX_RENDERER_VERSION_MESA);
10826 GLXQCR_INTEGER(GLX_RENDERER_ACCELERATED_MESA);
10827 GLXQCR_INTEGER(GLX_RENDERER_VIDEO_MEMORY_MESA);
10828 GLXQCR_INTEGER(GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA);
10829 GLXQCR_INTEGER(GLX_RENDERER_PREFERRED_PROFILE_MESA);
10830 GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA);
10831 GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA);
10832 GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA);
10833 GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA);
10834 GLXQCR_STRING(GLX_RENDERER_VENDOR_ID_MESA);
10835 GLXQCR_STRING(GLX_RENDERER_DEVICE_ID_MESA);
10836 }
10837 }
10838 #endif // SDL_VIDEO_DRIVER_X11
10839
10840 }
10841Index: source/ps/Mod.cpp
10842===================================================================
10843--- source/ps/Mod.cpp (revision 23275)
10844+++ source/ps/Mod.cpp (working copy)
10845@@ -1,158 +1,159 @@
10846 /* Copyright (C) 2019 Wildfire Games.
10847 * This file is part of 0 A.D.
10848 *
10849 * 0 A.D. is free software: you can redistribute it and/or modify
10850 * it under the terms of the GNU General Public License as published by
10851 * the Free Software Foundation, either version 2 of the License, or
10852 * (at your option) any later version.
10853 *
10854 * 0 A.D. is distributed in the hope that it will be useful,
10855 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10856 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10857 * GNU General Public License for more details.
10858 *
10859 * You should have received a copy of the GNU General Public License
10860 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
10861 */
10862
10863 #include "precompiled.h"
10864
10865 #include "ps/Mod.h"
10866
10867 #include <algorithm>
10868
10869 #include "lib/file/file_system.h"
10870 #include "lib/file/vfs/vfs.h"
10871 #include "lib/utf8.h"
10872 #include "ps/Filesystem.h"
10873 #include "ps/GameSetup/GameSetup.h"
10874 #include "ps/GameSetup/Paths.h"
10875+#include "ps/Pyrogenesis.h"
10876 #include "scriptinterface/ScriptInterface.h"
10877 #include "scriptinterface/ScriptRuntime.h"
10878
10879 std::vector<CStr> g_modsLoaded;
10880
10881 std::vector<std::vector<CStr>> g_LoadedModVersions;
10882
10883 CmdLineArgs g_args;
10884
10885 JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface)
10886 {
10887 JSContext* cx = scriptInterface.GetContext();
10888 JSAutoRequest rq(cx);
10889 JS::RootedObject obj(cx, JS_NewPlainObject(cx));
10890
10891 const Paths paths(g_args);
10892
10893 // loop over all possible paths
10894 OsPath modPath = paths.RData()/"mods";
10895 OsPath modUserPath = paths.UserData()/"mods";
10896
10897 DirectoryNames modDirs;
10898 DirectoryNames modDirsUser;
10899
10900 GetDirectoryEntries(modPath, NULL, &modDirs);
10901 // Sort modDirs so that we can do a fast lookup below
10902 std::sort(modDirs.begin(), modDirs.end());
10903
10904 PIVFS vfs = CreateVfs();
10905
10906 for (DirectoryNames::iterator iter = modDirs.begin(); iter != modDirs.end(); ++iter)
10907 {
10908 vfs->Clear();
10909 if (vfs->Mount(L"", modPath / *iter, VFS_MOUNT_MUST_EXIST) < 0)
10910 continue;
10911
10912 CVFSFile modinfo;
10913 if (modinfo.Load(vfs, L"mod.json", false) != PSRETURN_OK)
10914 continue;
10915
10916 JS::RootedValue json(cx);
10917 if (!scriptInterface.ParseJSON(modinfo.GetAsString(), &json))
10918 continue;
10919
10920 // Valid mod, add it to our structure
10921 JS_SetProperty(cx, obj, utf8_from_wstring(iter->string()).c_str(), json);
10922 }
10923
10924 GetDirectoryEntries(modUserPath, NULL, &modDirsUser);
10925 bool dev = InDevelopmentCopy();
10926
10927 for (DirectoryNames::iterator iter = modDirsUser.begin(); iter != modDirsUser.end(); ++iter)
10928 {
10929 // If we are in a dev copy we do not mount mods in the user mod folder that
10930 // are already present in the mod folder, thus we skip those here.
10931 if (dev && std::binary_search(modDirs.begin(), modDirs.end(), *iter))
10932 continue;
10933
10934 vfs->Clear();
10935 if (vfs->Mount(L"", modUserPath / *iter, VFS_MOUNT_MUST_EXIST) < 0)
10936 continue;
10937
10938 CVFSFile modinfo;
10939 if (modinfo.Load(vfs, L"mod.json", false) != PSRETURN_OK)
10940 continue;
10941
10942 JS::RootedValue json(cx);
10943 if (!scriptInterface.ParseJSON(modinfo.GetAsString(), &json))
10944 continue;
10945
10946 // Valid mod, add it to our structure
10947 JS_SetProperty(cx, obj, utf8_from_wstring(iter->string()).c_str(), json);
10948 }
10949
10950 return JS::ObjectValue(*obj);
10951 }
10952
10953 void Mod::CacheEnabledModVersions(const shared_ptr<ScriptRuntime>& scriptRuntime)
10954 {
10955 ScriptInterface scriptInterface("Engine", "CacheEnabledModVersions", scriptRuntime);
10956 JSContext* cx = scriptInterface.GetContext();
10957 JSAutoRequest rq(cx);
10958
10959 JS::RootedValue availableMods(cx, GetAvailableMods(scriptInterface));
10960
10961 g_LoadedModVersions.clear();
10962
10963 for (const CStr& mod : g_modsLoaded)
10964 {
10965 // Ignore user and mod mod as they are irrelevant for compatibility checks
10966 if (mod == "mod" || mod == "user")
10967 continue;
10968
10969 CStr version;
10970 JS::RootedValue modData(cx);
10971 if (scriptInterface.GetProperty(availableMods, mod.c_str(), &modData))
10972 scriptInterface.GetProperty(modData, "version", version);
10973
10974 g_LoadedModVersions.push_back({mod, version});
10975 }
10976 }
10977
10978 JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface)
10979 {
10980 JSContext* cx = scriptInterface.GetContext();
10981 JSAutoRequest rq(cx);
10982 JS::RootedValue returnValue(cx);
10983 scriptInterface.ToJSVal(cx, &returnValue, g_LoadedModVersions);
10984 return returnValue;
10985 }
10986
10987 JS::Value Mod::GetEngineInfo(const ScriptInterface& scriptInterface)
10988 {
10989 JSContext* cx = scriptInterface.GetContext();
10990 JSAutoRequest rq(cx);
10991
10992 JS::RootedValue mods(cx, Mod::GetLoadedModsWithVersions(scriptInterface));
10993 JS::RootedValue metainfo(cx);
10994
10995 ScriptInterface::CreateObject(
10996 cx,
10997 &metainfo,
10998 "engine_version", engine_version,
10999 "mods", mods);
11000
11001 scriptInterface.FreezeObject(metainfo, true);
11002
11003 return metainfo;
11004 }
11005Index: source/ps/ModIo.h
11006===================================================================
11007--- source/ps/ModIo.h (revision 23275)
11008+++ source/ps/ModIo.h (working copy)
11009@@ -1,208 +1,209 @@
11010 /* Copyright (C) 2018 Wildfire Games.
11011 *
11012 * Permission is hereby granted, free of charge, to any person obtaining
11013 * a copy of this software and associated documentation files (the
11014 * "Software"), to deal in the Software without restriction, including
11015 * without limitation the rights to use, copy, modify, merge, publish,
11016 * distribute, sublicense, and/or sell copies of the Software, and to
11017 * permit persons to whom the Software is furnished to do so, subject to
11018 * the following conditions:
11019 *
11020 * The above copyright notice and this permission notice shall be included
11021 * in all copies or substantial portions of the Software.
11022 *
11023 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
11024 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
11025 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11026 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
11027 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
11028 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
11029 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11030 */
11031
11032 #ifndef INCLUDED_MODIO
11033 #define INCLUDED_MODIO
11034
11035 #include "lib/external_libraries/curl.h"
11036+#include "lib/os_path.h"
11037 #include "scriptinterface/ScriptInterface.h"
11038
11039 #include <sodium.h>
11040 #include <string>
11041
11042 // TODO: Allocate instance of the below two using sodium_malloc?
11043 struct PKStruct
11044 {
11045 unsigned char sig_alg[2] = {}; // == "Ed"
11046 unsigned char keynum[8] = {}; // should match the keynum in the sigstruct, else this is the wrong key
11047 unsigned char pk[crypto_sign_PUBLICKEYBYTES] = {};
11048 };
11049
11050 struct SigStruct
11051 {
11052 unsigned char sig_alg[2] = {}; // "ED" (since we only support the hashed mode)
11053 unsigned char keynum[8] = {}; // should match the keynum in the PKStruct
11054 unsigned char sig[crypto_sign_BYTES] = {};
11055 };
11056
11057 struct ModIoModData
11058 {
11059 std::map<std::string, std::string> properties;
11060 std::vector<std::string> dependencies;
11061 SigStruct sig;
11062 };
11063
11064 enum class DownloadProgressStatus {
11065 NONE, // Default state
11066 GAMEID, // The game ID is being downloaded
11067 READY, // The game ID has been downloaded
11068 LISTING, // The mod list is being downloaded
11069 LISTED, // The mod list has been downloaded
11070 DOWNLOADING, // A mod file is being downloaded
11071 SUCCESS, // A mod file has been downloaded
11072
11073 FAILED_GAMEID, // Game ID couldn't be retrieved
11074 FAILED_LISTING, // Mod list couldn't be retrieved
11075 FAILED_DOWNLOADING, // File couldn't be retrieved
11076 FAILED_FILECHECK // The file is corrupted
11077 };
11078
11079 struct DownloadProgressData
11080 {
11081 DownloadProgressStatus status;
11082 double progress;
11083 std::string error;
11084 };
11085
11086 struct DownloadCallbackData;
11087
11088 /**
11089 * mod.io API interfacing code.
11090 *
11091 * Overview
11092 *
11093 * This class interfaces with a remote API provider that returns a list of mod files.
11094 * These can then be downloaded after some cursory checking of well-formedness of the returned
11095 * metadata.
11096 * Downloaded files are checked for well formedness by validating that they fit the size and hash
11097 * indicated by the API, then we check if the file is actually signed by a trusted key, and only
11098 * if all of that is success the file is actually possible to be loaded as a mod.
11099 *
11100 * Security considerations
11101 *
11102 * This both distrusts the loaded JS mods, and the API as much as possible.
11103 * We do not want a malicious mod to use this to download arbitrary files, nor do we want the API
11104 * to make us download something we have not verified.
11105 * Therefore we only allow mods to download one of the mods returned by this class (using indices).
11106 *
11107 * This (mostly) necessitates parsing the API responses here, as opposed to in JS.
11108 * One could alternatively parse the responses in a locked down JS context, but that would require
11109 * storing that code in here, or making sure nobody can overwrite it. Also this would possibly make
11110 * some of the needed accesses for downloading and verifying files a bit more complicated.
11111 *
11112 * Everything downloaded from the API has its signature verified against our public key.
11113 * This is a requirement, as otherwise a compromise of the API would result in users installing
11114 * possibly malicious files.
11115 * So a compromised API can just serve old files that we signed, so in that case there would need
11116 * to be an issue in that old file that was missed.
11117 *
11118 * To limit the extend to how old those files could be the signing key should be rotated
11119 * regularly (e.g. every release). To allow old versions of the engine to still use the API
11120 * files can be signed by both the old and the new key for some amount of time, that however
11121 * only makes sense in case a mod is compatible with both engine versions.
11122 *
11123 * Note that this does not prevent all possible attacks a package manager/update system should
11124 * defend against. This is intentionally not an update system since proper package managers already
11125 * exist. However there is some possible overlap in attack vectors and these should be evalutated
11126 * whether they apply and to what extend we can fix that on our side (or how to get the API provider
11127 * to help us do so). For a list of some possible issues see:
11128 * https://github.com/theupdateframework/specification/blob/master/tuf-spec.md
11129 *
11130 * The mod.io settings are also locked down such that only mods that have been authorized by us
11131 * show up in API queries. This is both done so that all required information (dependencies)
11132 * are stored for the files, and that only mods that have been checked for being ok are actually
11133 * shown to users.
11134 */
11135 class ModIo
11136 {
11137 NONCOPYABLE(ModIo);
11138 public:
11139 ModIo();
11140 ~ModIo();
11141
11142 // Async requests
11143 void StartGetGameId();
11144 void StartListMods();
11145 void StartDownloadMod(size_t idx);
11146
11147 /**
11148 * Advance the current async request and perform final steps if the download is complete.
11149 *
11150 * @param scriptInterface used for parsing the data and possibly install the mod.
11151 * @return true if the download is complete (successful or not), false otherwise.
11152 */
11153 bool AdvanceRequest(const ScriptInterface& scriptInterface);
11154
11155 /**
11156 * Cancel the current async request and clean things up
11157 */
11158 void CancelRequest();
11159
11160 const std::vector<ModIoModData>& GetMods() const
11161 {
11162 return m_ModData;
11163 }
11164 const DownloadProgressData& GetDownloadProgress() const
11165 {
11166 return m_DownloadProgressData;
11167 }
11168
11169 private:
11170 static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp);
11171 static size_t DownloadCallback(void* buffer, size_t size, size_t nmemb, void* userp);
11172 static int DownloadProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
11173
11174 CURLMcode SetupRequest(const std::string& url, bool fileDownload);
11175 void TearDownRequest();
11176
11177 bool ParseGameId(const ScriptInterface& scriptInterface, std::string& err);
11178 bool ParseMods(const ScriptInterface& scriptInterface, std::string& err);
11179
11180 void DeleteDownloadedFile();
11181 bool VerifyDownloadedFile(std::string& err);
11182
11183 // Utility methods for parsing mod.io responses and metadata
11184 static bool ParseGameIdResponse(const ScriptInterface& scriptInterface, const std::string& responseData, int& id, std::string& err);
11185 static bool ParseModsResponse(const ScriptInterface& scriptInterface, const std::string& responseData, std::vector<ModIoModData>& modData, const PKStruct& pk, std::string& err);
11186 static bool ParseSignature(const std::vector<std::string>& minisigs, SigStruct& sig, const PKStruct& pk, std::string& err);
11187
11188 // Url parts
11189 std::string m_BaseUrl;
11190 std::string m_GamesRequest;
11191 std::string m_GameId;
11192
11193 // Query parameters
11194 std::string m_ApiKey;
11195 std::string m_IdQuery;
11196
11197 CURL* m_Curl;
11198 CURLM* m_CurlMulti;
11199 curl_slist* m_Headers;
11200 char m_ErrorBuffer[CURL_ERROR_SIZE];
11201 std::string m_ResponseData;
11202
11203 // Current mod download
11204 int m_DownloadModID;
11205 OsPath m_DownloadFilePath;
11206 DownloadCallbackData* m_CallbackData;
11207 DownloadProgressData m_DownloadProgressData;
11208
11209 PKStruct m_pk;
11210
11211 std::vector<ModIoModData> m_ModData;
11212
11213 friend class TestModIo;
11214 };
11215
11216 extern ModIo* g_ModIo;
11217
11218 #endif // INCLUDED_MODIO
11219Index: source/ps/ProfileViewer.cpp
11220===================================================================
11221--- source/ps/ProfileViewer.cpp (revision 23275)
11222+++ source/ps/ProfileViewer.cpp (working copy)
11223@@ -1,624 +1,625 @@
11224 /* Copyright (C) 2019 Wildfire Games.
11225 * This file is part of 0 A.D.
11226 *
11227 * 0 A.D. is free software: you can redistribute it and/or modify
11228 * it under the terms of the GNU General Public License as published by
11229 * the Free Software Foundation, either version 2 of the License, or
11230 * (at your option) any later version.
11231 *
11232 * 0 A.D. is distributed in the hope that it will be useful,
11233 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11234 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11235 * GNU General Public License for more details.
11236 *
11237 * You should have received a copy of the GNU General Public License
11238 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
11239 */
11240
11241 /*
11242 * Implementation of profile display (containing only display routines,
11243 * the data model(s) are implemented elsewhere).
11244 */
11245
11246 #include "precompiled.h"
11247
11248 #include "ProfileViewer.h"
11249
11250 #include "graphics/FontMetrics.h"
11251 #include "graphics/ShaderManager.h"
11252 #include "graphics/TextRenderer.h"
11253 #include "gui/GUIMatrix.h"
11254 #include "lib/external_libraries/libsdl.h"
11255 #include "ps/CLogger.h"
11256 #include "ps/Filesystem.h"
11257 #include "ps/Hotkey.h"
11258 #include "ps/Profile.h"
11259+#include "ps/Pyrogenesis.h"
11260 #include "renderer/Renderer.h"
11261 #include "scriptinterface/ScriptInterface.h"
11262
11263 #include <algorithm>
11264 #include <ctime>
11265
11266 extern int g_xres, g_yres;
11267
11268 struct CProfileViewerInternals
11269 {
11270 NONCOPYABLE(CProfileViewerInternals); // because of the ofstream
11271 public:
11272 CProfileViewerInternals() {}
11273
11274 /// Whether the profiling display is currently visible
11275 bool profileVisible;
11276
11277 /// List of root tables
11278 std::vector<AbstractProfileTable*> rootTables;
11279
11280 /// Path from a root table (path[0]) to the currently visible table (path[size-1])
11281 std::vector<AbstractProfileTable*> path;
11282
11283 /// Helper functions
11284 void TableIsDeleted(AbstractProfileTable* table);
11285 void NavigateTree(int id);
11286
11287 /// File for saved profile output (reset when the game is restarted)
11288 std::ofstream outputStream;
11289 };
11290
11291
11292 ///////////////////////////////////////////////////////////////////////////////////////////////
11293 // AbstractProfileTable implementation
11294
11295 AbstractProfileTable::~AbstractProfileTable()
11296 {
11297 if (CProfileViewer::IsInitialised())
11298 {
11299 g_ProfileViewer.m->TableIsDeleted(this);
11300 }
11301 }
11302
11303
11304 ///////////////////////////////////////////////////////////////////////////////////////////////
11305 // CProfileViewer implementation
11306
11307
11308 // AbstractProfileTable got deleted, make sure we have no dangling pointers
11309 void CProfileViewerInternals::TableIsDeleted(AbstractProfileTable* table)
11310 {
11311 for(int idx = (int)rootTables.size()-1; idx >= 0; --idx)
11312 {
11313 if (rootTables[idx] == table)
11314 rootTables.erase(rootTables.begin() + idx);
11315 }
11316
11317 for(size_t idx = 0; idx < path.size(); ++idx)
11318 {
11319 if (path[idx] != table)
11320 continue;
11321
11322 path.erase(path.begin() + idx, path.end());
11323 if (path.size() == 0)
11324 profileVisible = false;
11325 }
11326 }
11327
11328
11329 // Move into child tables or return to parent tables based on the given number
11330 void CProfileViewerInternals::NavigateTree(int id)
11331 {
11332 if (id == 0)
11333 {
11334 if (path.size() > 1)
11335 path.pop_back();
11336 }
11337 else
11338 {
11339 AbstractProfileTable* table = path[path.size() - 1];
11340 size_t numrows = table->GetNumberRows();
11341
11342 for(size_t row = 0; row < numrows; ++row)
11343 {
11344 AbstractProfileTable* child = table->GetChild(row);
11345
11346 if (!child)
11347 continue;
11348
11349 --id;
11350 if (id == 0)
11351 {
11352 path.push_back(child);
11353 break;
11354 }
11355 }
11356 }
11357 }
11358
11359
11360 // Construction/Destruction
11361 CProfileViewer::CProfileViewer()
11362 {
11363 m = new CProfileViewerInternals;
11364 m->profileVisible = false;
11365 }
11366
11367 CProfileViewer::~CProfileViewer()
11368 {
11369 delete m;
11370 }
11371
11372
11373 // Render
11374 void CProfileViewer::RenderProfile()
11375 {
11376 if (!m->profileVisible)
11377 return;
11378
11379 if (!m->path.size())
11380 {
11381 m->profileVisible = false;
11382 return;
11383 }
11384
11385 PROFILE3_GPU("profile viewer");
11386
11387 glEnable(GL_BLEND);
11388 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
11389
11390 AbstractProfileTable* table = m->path[m->path.size() - 1];
11391 const std::vector<ProfileColumn>& columns = table->GetColumns();
11392 size_t numrows = table->GetNumberRows();
11393
11394 CStrIntern font_name("mono-stroke-10");
11395 CFontMetrics font(font_name);
11396 int lineSpacing = font.GetLineSpacing();
11397
11398 // Render background
11399 GLint estimate_height;
11400 GLint estimate_width;
11401
11402 estimate_width = 50;
11403 for(size_t i = 0; i < columns.size(); ++i)
11404 estimate_width += (GLint)columns[i].width;
11405
11406 estimate_height = 3 + (GLint)numrows;
11407 if (m->path.size() > 1)
11408 estimate_height += 2;
11409 estimate_height = lineSpacing*estimate_height;
11410
11411 CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
11412 solidTech->BeginPass();
11413 CShaderProgramPtr solidShader = solidTech->GetShader();
11414
11415 solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.5f);
11416
11417 CMatrix3D transform = GetDefaultGuiMatrix();
11418 solidShader->Uniform(str_transform, transform);
11419
11420 float backgroundVerts[] = {
11421 (float)estimate_width, 0.0f,
11422 0.0f, 0.0f,
11423 0.0f, (float)estimate_height,
11424 0.0f, (float)estimate_height,
11425 (float)estimate_width, (float)estimate_height,
11426 (float)estimate_width, 0.0f
11427 };
11428 solidShader->VertexPointer(2, GL_FLOAT, 0, backgroundVerts);
11429 solidShader->AssertPointersBound();
11430 glDrawArrays(GL_TRIANGLES, 0, 6);
11431
11432 transform.PostTranslate(22.0f, lineSpacing*3.0f, 0.0f);
11433 solidShader->Uniform(str_transform, transform);
11434
11435 // Draw row backgrounds
11436 for (size_t row = 0; row < numrows; ++row)
11437 {
11438 if (row % 2)
11439 solidShader->Uniform(str_color, 1.0f, 1.0f, 1.0f, 0.1f);
11440 else
11441 solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.1f);
11442
11443 float rowVerts[] = {
11444 -22.f, 2.f,
11445 estimate_width-22.f, 2.f,
11446 estimate_width-22.f, 2.f-lineSpacing,
11447
11448 estimate_width-22.f, 2.f-lineSpacing,
11449 -22.f, 2.f-lineSpacing,
11450 -22.f, 2.f
11451 };
11452 solidShader->VertexPointer(2, GL_FLOAT, 0, rowVerts);
11453 solidShader->AssertPointersBound();
11454 glDrawArrays(GL_TRIANGLES, 0, 6);
11455
11456 transform.PostTranslate(0.0f, lineSpacing, 0.0f);
11457 solidShader->Uniform(str_transform, transform);
11458 }
11459
11460 solidTech->EndPass();
11461
11462 // Print table and column titles
11463
11464 CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
11465 textTech->BeginPass();
11466
11467 CTextRenderer textRenderer(textTech->GetShader());
11468 textRenderer.Font(font_name);
11469 textRenderer.Color(1.0f, 1.0f, 1.0f);
11470
11471 textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str());
11472
11473 textRenderer.Translate(22.0f, lineSpacing*2.0f, 0.0f);
11474
11475 float colX = 0.0f;
11476 for (size_t col = 0; col < columns.size(); ++col)
11477 {
11478 CStrW text = columns[col].title.FromUTF8();
11479 int w, h;
11480 font.CalculateStringSize(text.c_str(), w, h);
11481
11482 float x = colX;
11483 if (col > 0) // right-align all but the first column
11484 x += columns[col].width - w;
11485 textRenderer.Put(x, 0.0f, text.c_str());
11486
11487 colX += columns[col].width;
11488 }
11489
11490 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
11491
11492 // Print rows
11493 int currentExpandId = 1;
11494
11495 for (size_t row = 0; row < numrows; ++row)
11496 {
11497 if (table->IsHighlightRow(row))
11498 textRenderer.Color(1.0f, 0.5f, 0.5f);
11499 else
11500 textRenderer.Color(1.0f, 1.0f, 1.0f);
11501
11502 if (table->GetChild(row))
11503 {
11504 textRenderer.PrintfAt(-15.0f, 0.0f, L"%d", currentExpandId);
11505 currentExpandId++;
11506 }
11507
11508 float colX = 0.0f;
11509 for (size_t col = 0; col < columns.size(); ++col)
11510 {
11511 CStrW text = table->GetCellText(row, col).FromUTF8();
11512 int w, h;
11513 font.CalculateStringSize(text.c_str(), w, h);
11514
11515 float x = colX;
11516 if (col > 0) // right-align all but the first column
11517 x += columns[col].width - w;
11518 textRenderer.Put(x, 0.0f, text.c_str());
11519
11520 colX += columns[col].width;
11521 }
11522
11523 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
11524 }
11525
11526 textRenderer.Color(1.0f, 1.0f, 1.0f);
11527
11528 if (m->path.size() > 1)
11529 {
11530 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
11531 textRenderer.Put(-15.0f, 0.0f, L"0");
11532 textRenderer.Put(0.0f, 0.0f, L"back to parent");
11533 }
11534
11535 textRenderer.Render();
11536 textTech->EndPass();
11537
11538 glDisable(GL_BLEND);
11539
11540 glEnable(GL_DEPTH_TEST);
11541 }
11542
11543
11544 // Handle input
11545 InReaction CProfileViewer::Input(const SDL_Event_* ev)
11546 {
11547 switch(ev->ev.type)
11548 {
11549 case SDL_KEYDOWN:
11550 {
11551 if (!m->profileVisible)
11552 break;
11553
11554 int k = ev->ev.key.keysym.sym;
11555 if (k >= SDLK_0 && k <= SDLK_9)
11556 {
11557 m->NavigateTree(k - SDLK_0);
11558 return IN_HANDLED;
11559 }
11560 break;
11561 }
11562 case SDL_HOTKEYDOWN:
11563 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
11564
11565 if( hotkey == "profile.toggle" )
11566 {
11567 if (!m->profileVisible)
11568 {
11569 if (m->rootTables.size())
11570 {
11571 m->profileVisible = true;
11572 m->path.push_back(m->rootTables[0]);
11573 }
11574 }
11575 else
11576 {
11577 size_t i;
11578
11579 for(i = 0; i < m->rootTables.size(); ++i)
11580 {
11581 if (m->rootTables[i] == m->path[0])
11582 break;
11583 }
11584 i++;
11585
11586 m->path.clear();
11587 if (i < m->rootTables.size())
11588 {
11589 m->path.push_back(m->rootTables[i]);
11590 }
11591 else
11592 {
11593 m->profileVisible = false;
11594 }
11595 }
11596 return( IN_HANDLED );
11597 }
11598 else if( hotkey == "profile.save" )
11599 {
11600 SaveToFile();
11601 return( IN_HANDLED );
11602 }
11603 break;
11604 }
11605 return( IN_PASS );
11606 }
11607
11608 InReaction CProfileViewer::InputThunk(const SDL_Event_* ev)
11609 {
11610 if (CProfileViewer::IsInitialised())
11611 return g_ProfileViewer.Input(ev);
11612
11613 return IN_PASS;
11614 }
11615
11616
11617 // Add a table to the list of roots
11618 void CProfileViewer::AddRootTable(AbstractProfileTable* table, bool front)
11619 {
11620 if (front)
11621 m->rootTables.insert(m->rootTables.begin(), table);
11622 else
11623 m->rootTables.push_back(table);
11624 }
11625
11626 namespace
11627 {
11628 struct WriteTable
11629 {
11630 std::ofstream& f;
11631 WriteTable(std::ofstream& f) : f(f) {}
11632
11633 void operator() (AbstractProfileTable* table)
11634 {
11635 std::vector<CStr> data; // 2d array of (rows+head)*columns elements
11636
11637 const std::vector<ProfileColumn>& columns = table->GetColumns();
11638
11639 // Add column headers to 'data'
11640 for (std::vector<ProfileColumn>::const_iterator col_it = columns.begin();
11641 col_it != columns.end(); ++col_it)
11642 data.push_back(col_it->title);
11643
11644 // Recursively add all profile data to 'data'
11645 WriteRows(1, table, data);
11646
11647 // Calculate the width of each column ( = the maximum width of
11648 // any value in that column)
11649 std::vector<size_t> columnWidths;
11650 size_t cols = columns.size();
11651 for (size_t c = 0; c < cols; ++c)
11652 {
11653 size_t max = 0;
11654 for (size_t i = c; i < data.size(); i += cols)
11655 max = std::max(max, data[i].length());
11656 columnWidths.push_back(max);
11657 }
11658
11659 // Output data as a formatted table:
11660
11661 f << "\n\n" << table->GetTitle() << "\n";
11662
11663 if (cols == 0) // avoid divide-by-zero
11664 return;
11665
11666 for (size_t r = 0; r < data.size()/cols; ++r)
11667 {
11668 for (size_t c = 0; c < cols; ++c)
11669 f << (c ? " | " : "\n")
11670 << data[r*cols + c].Pad(PS_TRIM_RIGHT, columnWidths[c]);
11671
11672 // Add dividers under some rows. (Currently only the first, since
11673 // that contains the column headers.)
11674 if (r == 0)
11675 for (size_t c = 0; c < cols; ++c)
11676 f << (c ? "-|-" : "\n")
11677 << CStr::Repeat("-", columnWidths[c]);
11678 }
11679 }
11680
11681 void WriteRows(int indent, AbstractProfileTable* table, std::vector<CStr>& data)
11682 {
11683 const std::vector<ProfileColumn>& columns = table->GetColumns();
11684
11685 for (size_t r = 0; r < table->GetNumberRows(); ++r)
11686 {
11687 // Do pretty tree-structure indenting
11688 CStr indentation = CStr::Repeat("| ", indent-1);
11689 if (r+1 == table->GetNumberRows())
11690 indentation += "'-";
11691 else
11692 indentation += "|-";
11693
11694 for (size_t c = 0; c < columns.size(); ++c)
11695 if (c == 0)
11696 data.push_back(indentation + table->GetCellText(r, c));
11697 else
11698 data.push_back(table->GetCellText(r, c));
11699
11700 if (table->GetChild(r))
11701 WriteRows(indent+1, table->GetChild(r), data);
11702 }
11703 }
11704
11705 private:
11706 const WriteTable& operator=(const WriteTable&);
11707 };
11708
11709 struct DumpTable
11710 {
11711 const ScriptInterface& m_ScriptInterface;
11712 JS::PersistentRooted<JS::Value> m_Root;
11713 DumpTable(const ScriptInterface& scriptInterface, JS::HandleValue root) :
11714 m_ScriptInterface(scriptInterface), m_Root(scriptInterface.GetJSRuntime(), root)
11715 {
11716 }
11717
11718 // std::for_each requires a move constructor and the use of JS::PersistentRooted<T> apparently breaks a requirement for an
11719 // automatic move constructor
11720 DumpTable(DumpTable && original) :
11721 m_ScriptInterface(original.m_ScriptInterface),
11722 m_Root(original.m_ScriptInterface.GetJSRuntime(), original.m_Root.get())
11723 {
11724 }
11725
11726 void operator() (AbstractProfileTable* table)
11727 {
11728 JSContext* cx = m_ScriptInterface.GetContext();
11729 JSAutoRequest rq(cx);
11730
11731 JS::RootedValue t(cx);
11732 ScriptInterface::CreateObject(
11733 cx,
11734 &t,
11735 "cols", DumpCols(table),
11736 "data", DumpRows(table));
11737
11738 m_ScriptInterface.SetProperty(m_Root, table->GetTitle().c_str(), t);
11739 }
11740
11741 std::vector<std::string> DumpCols(AbstractProfileTable* table)
11742 {
11743 std::vector<std::string> titles;
11744
11745 const std::vector<ProfileColumn>& columns = table->GetColumns();
11746
11747 for (size_t c = 0; c < columns.size(); ++c)
11748 titles.push_back(columns[c].title);
11749
11750 return titles;
11751 }
11752
11753 JS::Value DumpRows(AbstractProfileTable* table)
11754 {
11755 JSContext* cx = m_ScriptInterface.GetContext();
11756 JSAutoRequest rq(cx);
11757
11758 JS::RootedValue data(cx);
11759 ScriptInterface::CreateObject(cx, &data);
11760
11761 const std::vector<ProfileColumn>& columns = table->GetColumns();
11762
11763 for (size_t r = 0; r < table->GetNumberRows(); ++r)
11764 {
11765 JS::RootedValue row(cx);
11766 ScriptInterface::CreateArray(cx, &row);
11767
11768 m_ScriptInterface.SetProperty(data, table->GetCellText(r, 0).c_str(), row);
11769
11770 if (table->GetChild(r))
11771 {
11772 JS::RootedValue childRows(cx, DumpRows(table->GetChild(r)));
11773 m_ScriptInterface.SetPropertyInt(row, 0, childRows);
11774 }
11775
11776 for (size_t c = 1; c < columns.size(); ++c)
11777 m_ScriptInterface.SetPropertyInt(row, c, table->GetCellText(r, c));
11778 }
11779
11780 return data;
11781 }
11782
11783 private:
11784 const DumpTable& operator=(const DumpTable&);
11785 };
11786
11787 bool SortByName(AbstractProfileTable* a, AbstractProfileTable* b)
11788 {
11789 return (a->GetName() < b->GetName());
11790 }
11791 }
11792
11793 void CProfileViewer::SaveToFile()
11794 {
11795 // Open the file, if necessary. If this method is called several times,
11796 // the profile results will be appended to the previous ones from the same
11797 // run.
11798 if (! m->outputStream.is_open())
11799 {
11800 // Open the file. (It will be closed when the CProfileViewer
11801 // destructor is called.)
11802 OsPath path = psLogDir()/"profile.txt";
11803 m->outputStream.open(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
11804
11805 if (m->outputStream.fail())
11806 {
11807 LOGERROR("Failed to open profile log file");
11808 return;
11809 }
11810 else
11811 {
11812 LOGMESSAGERENDER("Profiler snapshot saved to '%s'", path.string8());
11813 }
11814 }
11815
11816 time_t t;
11817 time(&t);
11818 m->outputStream << "================================================================\n\n";
11819 m->outputStream << "PS profiler snapshot - " << asctime(localtime(&t));
11820
11821 std::vector<AbstractProfileTable*> tables = m->rootTables;
11822 sort(tables.begin(), tables.end(), SortByName);
11823 for_each(tables.begin(), tables.end(), WriteTable(m->outputStream));
11824
11825 m->outputStream << "\n\n================================================================\n";
11826 m->outputStream.flush();
11827 }
11828
11829 void CProfileViewer::ShowTable(const CStr& table)
11830 {
11831 m->path.clear();
11832
11833 if (table.length() > 0)
11834 {
11835 for (size_t i = 0; i < m->rootTables.size(); ++i)
11836 {
11837 if (m->rootTables[i]->GetName() == table)
11838 {
11839 m->path.push_back(m->rootTables[i]);
11840 m->profileVisible = true;
11841 return;
11842 }
11843 }
11844 }
11845
11846 // No matching table found, so don't display anything
11847 m->profileVisible = false;
11848 }
11849Index: source/ps/Profiler2.cpp
11850===================================================================
11851--- source/ps/Profiler2.cpp (revision 23275)
11852+++ source/ps/Profiler2.cpp (working copy)
11853@@ -1,981 +1,983 @@
11854 /* Copyright (C) 2019 Wildfire Games.
11855 *
11856 * Permission is hereby granted, free of charge, to any person obtaining
11857 * a copy of this software and associated documentation files (the
11858 * "Software"), to deal in the Software without restriction, including
11859 * without limitation the rights to use, copy, modify, merge, publish,
11860 * distribute, sublicense, and/or sell copies of the Software, and to
11861 * permit persons to whom the Software is furnished to do so, subject to
11862 * the following conditions:
11863 *
11864 * The above copyright notice and this permission notice shall be included
11865 * in all copies or substantial portions of the Software.
11866 *
11867 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
11868 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
11869 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11870 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
11871 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
11872 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
11873 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11874 */
11875
11876 #include "precompiled.h"
11877
11878 #include "Profiler2.h"
11879
11880 #include "lib/allocators/shared_ptr.h"
11881+#include "lib/os_path.h"
11882 #include "ps/CLogger.h"
11883 #include "ps/CStr.h"
11884 #include "ps/Profiler2GPU.h"
11885+#include "ps/Pyrogenesis.h"
11886 #include "third_party/mongoose/mongoose.h"
11887
11888 #include <iomanip>
11889 #include <map>
11890 #include <unordered_map>
11891
11892 CProfiler2 g_Profiler2;
11893
11894 const size_t CProfiler2::MAX_ATTRIBUTE_LENGTH = 256;
11895
11896 // TODO: what's a good size?
11897 const size_t CProfiler2::BUFFER_SIZE = 4 * 1024 * 1024;
11898 const size_t CProfiler2::HOLD_BUFFER_SIZE = 128 * 1024;
11899
11900 // A human-recognisable pattern (for debugging) followed by random bytes (for uniqueness)
11901 const u8 CProfiler2::RESYNC_MAGIC[8] = {0x11, 0x22, 0x33, 0x44, 0xf4, 0x93, 0xbe, 0x15};
11902
11903 thread_local CProfiler2::ThreadStorage* CProfiler2::m_CurrentStorage = nullptr;
11904
11905 CProfiler2::CProfiler2() :
11906 m_Initialised(false), m_FrameNumber(0), m_MgContext(NULL), m_GPU(NULL)
11907 {
11908 }
11909
11910 CProfiler2::~CProfiler2()
11911 {
11912 if (m_Initialised)
11913 Shutdown();
11914 }
11915
11916 /**
11917 * Mongoose callback. Run in an arbitrary thread (possibly concurrently with other requests).
11918 */
11919 static void* MgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
11920 {
11921 CProfiler2* profiler = (CProfiler2*)request_info->user_data;
11922 ENSURE(profiler);
11923
11924 void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling
11925
11926 const char* header200 =
11927 "HTTP/1.1 200 OK\r\n"
11928 "Access-Control-Allow-Origin: *\r\n" // TODO: not great for security
11929 "Content-Type: text/plain; charset=utf-8\r\n\r\n";
11930
11931 const char* header404 =
11932 "HTTP/1.1 404 Not Found\r\n"
11933 "Content-Type: text/plain; charset=utf-8\r\n\r\n"
11934 "Unrecognised URI";
11935
11936 const char* header400 =
11937 "HTTP/1.1 400 Bad Request\r\n"
11938 "Content-Type: text/plain; charset=utf-8\r\n\r\n"
11939 "Invalid request";
11940
11941 switch (event)
11942 {
11943 case MG_NEW_REQUEST:
11944 {
11945 std::stringstream stream;
11946
11947 std::string uri = request_info->uri;
11948
11949 if (uri == "/download")
11950 {
11951 profiler->SaveToFile();
11952 }
11953 else if (uri == "/overview")
11954 {
11955 profiler->ConstructJSONOverview(stream);
11956 }
11957 else if (uri == "/query")
11958 {
11959 if (!request_info->query_string)
11960 {
11961 mg_printf(conn, "%s (no query string)", header400);
11962 return handled;
11963 }
11964
11965 // Identify the requested thread
11966 char buf[256];
11967 int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), "thread", buf, ARRAY_SIZE(buf));
11968 if (len < 0)
11969 {
11970 mg_printf(conn, "%s (no 'thread')", header400);
11971 return handled;
11972 }
11973 std::string thread(buf);
11974
11975 const char* err = profiler->ConstructJSONResponse(stream, thread);
11976 if (err)
11977 {
11978 mg_printf(conn, "%s (%s)", header400, err);
11979 return handled;
11980 }
11981 }
11982 else
11983 {
11984 mg_printf(conn, "%s", header404);
11985 return handled;
11986 }
11987
11988 mg_printf(conn, "%s", header200);
11989 std::string str = stream.str();
11990 mg_write(conn, str.c_str(), str.length());
11991 return handled;
11992 }
11993
11994 case MG_HTTP_ERROR:
11995 return NULL;
11996
11997 case MG_EVENT_LOG:
11998 // Called by Mongoose's cry()
11999 LOGERROR("Mongoose error: %s", request_info->log_message);
12000 return NULL;
12001
12002 case MG_INIT_SSL:
12003 return NULL;
12004
12005 default:
12006 debug_warn(L"Invalid Mongoose event type");
12007 return NULL;
12008 }
12009 };
12010
12011 void CProfiler2::Initialise()
12012 {
12013 ENSURE(!m_Initialised);
12014 m_Initialised = true;
12015
12016 RegisterCurrentThread("main");
12017 }
12018
12019 void CProfiler2::InitialiseGPU()
12020 {
12021 ENSURE(!m_GPU);
12022 m_GPU = new CProfiler2GPU(*this);
12023 }
12024
12025 void CProfiler2::EnableHTTP()
12026 {
12027 ENSURE(m_Initialised);
12028 LOGMESSAGERENDER("Starting profiler2 HTTP server");
12029
12030 // Ignore multiple enablings
12031 if (m_MgContext)
12032 return;
12033
12034 const char *options[] = {
12035 "listening_ports", "127.0.0.1:8000", // bind to localhost for security
12036 "num_threads", "6", // enough for the browser's parallel connection limit
12037 NULL
12038 };
12039 m_MgContext = mg_start(MgCallback, this, options);
12040 ENSURE(m_MgContext);
12041 }
12042
12043 void CProfiler2::EnableGPU()
12044 {
12045 ENSURE(m_Initialised);
12046 if (!m_GPU)
12047 {
12048 LOGMESSAGERENDER("Starting profiler2 GPU mode");
12049 InitialiseGPU();
12050 }
12051 }
12052
12053 void CProfiler2::ShutdownGPU()
12054 {
12055 LOGMESSAGERENDER("Shutting down profiler2 GPU mode");
12056 SAFE_DELETE(m_GPU);
12057 }
12058
12059 void CProfiler2::ShutDownHTTP()
12060 {
12061 LOGMESSAGERENDER("Shutting down profiler2 HTTP server");
12062 if (m_MgContext)
12063 {
12064 mg_stop(m_MgContext);
12065 m_MgContext = NULL;
12066 }
12067 }
12068
12069 void CProfiler2::Toggle()
12070 {
12071 // TODO: Maybe we can open the browser to the profiler page automatically
12072 if (m_GPU && m_MgContext)
12073 {
12074 ShutdownGPU();
12075 ShutDownHTTP();
12076 }
12077 else if (!m_GPU && !m_MgContext)
12078 {
12079 EnableGPU();
12080 EnableHTTP();
12081 }
12082 }
12083
12084 void CProfiler2::Shutdown()
12085 {
12086 ENSURE(m_Initialised);
12087
12088 ENSURE(!m_GPU); // must shutdown GPU before profiler
12089
12090 if (m_MgContext)
12091 {
12092 mg_stop(m_MgContext);
12093 m_MgContext = NULL;
12094 }
12095
12096 // the destructor is not called for the main thread
12097 // we have to call it manually to avoid memory leaks
12098 ENSURE(ThreadUtil::IsMainThread());
12099 m_Initialised = false;
12100 }
12101
12102 void CProfiler2::RecordGPUFrameStart()
12103 {
12104 if (m_GPU)
12105 m_GPU->FrameStart();
12106 }
12107
12108 void CProfiler2::RecordGPUFrameEnd()
12109 {
12110 if (m_GPU)
12111 m_GPU->FrameEnd();
12112 }
12113
12114 void CProfiler2::RecordGPURegionEnter(const char* id)
12115 {
12116 if (m_GPU)
12117 m_GPU->RegionEnter(id);
12118 }
12119
12120 void CProfiler2::RecordGPURegionLeave(const char* id)
12121 {
12122 if (m_GPU)
12123 m_GPU->RegionLeave(id);
12124 }
12125
12126 void CProfiler2::RegisterCurrentThread(const std::string& name)
12127 {
12128 ENSURE(m_Initialised);
12129
12130 // Must not register a thread more than once.
12131 ENSURE(m_CurrentStorage == nullptr);
12132
12133 m_CurrentStorage = new ThreadStorage(*this, name);
12134 AddThreadStorage(m_CurrentStorage);
12135
12136 RecordSyncMarker();
12137 RecordEvent("thread start");
12138 }
12139
12140 void CProfiler2::AddThreadStorage(ThreadStorage* storage)
12141 {
12142 std::lock_guard<std::mutex> lock(m_Mutex);
12143 m_Threads.push_back(std::unique_ptr<ThreadStorage>(storage));
12144 }
12145
12146 void CProfiler2::RemoveThreadStorage(ThreadStorage* storage)
12147 {
12148 std::lock_guard<std::mutex> lock(m_Mutex);
12149 m_Threads.erase(std::find_if(m_Threads.begin(), m_Threads.end(), [storage](const std::unique_ptr<ThreadStorage>& s) { return s.get() == storage; }));
12150 }
12151
12152 CProfiler2::ThreadStorage::ThreadStorage(CProfiler2& profiler, const std::string& name) :
12153 m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time()), m_HeldDepth(0)
12154 {
12155 m_Buffer = new u8[BUFFER_SIZE];
12156 memset(m_Buffer, ITEM_NOP, BUFFER_SIZE);
12157 }
12158
12159 CProfiler2::ThreadStorage::~ThreadStorage()
12160 {
12161 delete[] m_Buffer;
12162 }
12163
12164 void CProfiler2::ThreadStorage::Write(EItem type, const void* item, u32 itemSize)
12165 {
12166 if (m_HeldDepth > 0)
12167 {
12168 WriteHold(type, item, itemSize);
12169 return;
12170 }
12171 // See m_BufferPos0 etc for comments on synchronisation
12172
12173 u32 size = 1 + itemSize;
12174 u32 start = m_BufferPos0;
12175 if (start + size > BUFFER_SIZE)
12176 {
12177 // The remainder of the buffer is too small - fill the rest
12178 // with NOPs then start from offset 0, so we don't have to
12179 // bother splitting the real item across the end of the buffer
12180
12181 m_BufferPos0 = size;
12182 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
12183
12184 memset(m_Buffer + start, 0, BUFFER_SIZE - start);
12185 start = 0;
12186 }
12187 else
12188 {
12189 m_BufferPos0 = start + size;
12190 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
12191 }
12192
12193 m_Buffer[start] = (u8)type;
12194 memcpy(&m_Buffer[start + 1], item, itemSize);
12195
12196 COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer
12197 m_BufferPos1 = start + size;
12198 }
12199
12200 void CProfiler2::ThreadStorage::WriteHold(EItem type, const void* item, u32 itemSize)
12201 {
12202 u32 size = 1 + itemSize;
12203
12204 if (m_HoldBuffers[m_HeldDepth - 1].pos + size > CProfiler2::HOLD_BUFFER_SIZE)
12205 return; // we held on too much data, ignore the rest
12206
12207 m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos] = (u8)type;
12208 memcpy(&m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos + 1], item, itemSize);
12209
12210 m_HoldBuffers[m_HeldDepth - 1].pos += size;
12211 }
12212
12213 std::string CProfiler2::ThreadStorage::GetBuffer()
12214 {
12215 // Called from an arbitrary thread (not the one writing to the buffer).
12216 //
12217 // See comments on m_BufferPos0 etc.
12218
12219 shared_ptr<u8> buffer(new u8[BUFFER_SIZE], ArrayDeleter());
12220
12221 u32 pos1 = m_BufferPos1;
12222 COMPILER_FENCE; // must read m_BufferPos1 before m_Buffer
12223
12224 memcpy(buffer.get(), m_Buffer, BUFFER_SIZE);
12225
12226 COMPILER_FENCE; // must read m_BufferPos0 after m_Buffer
12227 u32 pos0 = m_BufferPos0;
12228
12229 // The range [pos1, pos0) modulo BUFFER_SIZE is invalid, so concatenate the rest of the buffer
12230
12231 if (pos1 <= pos0) // invalid range is in the middle of the buffer
12232 return std::string(buffer.get()+pos0, buffer.get()+BUFFER_SIZE) + std::string(buffer.get(), buffer.get()+pos1);
12233 else // invalid wrap is wrapped around the end/start buffer
12234 return std::string(buffer.get()+pos0, buffer.get()+pos1);
12235 }
12236
12237 void CProfiler2::ThreadStorage::RecordAttribute(const char* fmt, va_list argp)
12238 {
12239 char buffer[MAX_ATTRIBUTE_LENGTH + 4] = {0}; // first 4 bytes are used for storing length
12240 int len = vsnprintf(buffer + 4, MAX_ATTRIBUTE_LENGTH - 1, fmt, argp); // subtract 1 from length to make MSVC vsnprintf safe
12241 // (Don't use vsprintf_s because it treats overflow as fatal)
12242
12243 // Terminate the string if the printing was truncated
12244 if (len < 0 || len >= (int)MAX_ATTRIBUTE_LENGTH - 1)
12245 {
12246 strncpy(buffer + 4 + MAX_ATTRIBUTE_LENGTH - 4, "...", 4);
12247 len = MAX_ATTRIBUTE_LENGTH - 1; // excluding null terminator
12248 }
12249
12250 // Store the length in the buffer
12251 memcpy(buffer, &len, sizeof(len));
12252
12253 Write(ITEM_ATTRIBUTE, buffer, 4 + len);
12254 }
12255
12256 size_t CProfiler2::ThreadStorage::HoldLevel()
12257 {
12258 return m_HeldDepth;
12259 }
12260
12261 u8 CProfiler2::ThreadStorage::HoldType()
12262 {
12263 return m_HoldBuffers[m_HeldDepth - 1].type;
12264 }
12265
12266 void CProfiler2::ThreadStorage::PutOnHold(u8 newType)
12267 {
12268 m_HeldDepth++;
12269 m_HoldBuffers[m_HeldDepth - 1].clear();
12270 m_HoldBuffers[m_HeldDepth - 1].setType(newType);
12271 }
12272
12273 // this flattens the stack, use it sensibly
12274 void rewriteBuffer(u8* buffer, u32& bufferSize)
12275 {
12276 double startTime = timer_Time();
12277
12278 u32 size = bufferSize;
12279 u32 readPos = 0;
12280
12281 double initialTime = -1;
12282 double total_time = -1;
12283 const char* regionName;
12284 std::set<std::string> topLevelArgs;
12285
12286 using infoPerType = std::tuple<const char*, double, std::set<std::string> >;
12287 using timeByTypeMap = std::unordered_map<std::string, infoPerType>;
12288
12289 timeByTypeMap timeByType;
12290 std::vector<double> last_time_stack;
12291 std::vector<const char*> last_names;
12292
12293 // never too many hacks
12294 std::string current_attribute = "";
12295 std::map<std::string, double> time_per_attribute;
12296
12297 // Let's read the first event
12298 {
12299 u8 type = buffer[readPos];
12300 ++readPos;
12301 if (type != CProfiler2::ITEM_ENTER)
12302 {
12303 debug_warn("Profiler2: Condensing a region should run into ITEM_ENTER first");
12304 return; // do nothing
12305 }
12306 CProfiler2::SItem_dt_id item;
12307 memcpy(&item, buffer + readPos, sizeof(item));
12308 readPos += sizeof(item);
12309
12310 regionName = item.id;
12311 last_names.push_back(item.id);
12312 initialTime = (double)item.dt;
12313 }
12314 int enter = 1;
12315 int leaves = 0;
12316 // Read subsequent events. Flatten hierarchy because it would get too complicated otherwise.
12317 // To make sure time doesn't bloat, subtract time from nested events
12318 while (readPos < size)
12319 {
12320 u8 type = buffer[readPos];
12321 ++readPos;
12322
12323 switch (type)
12324 {
12325 case CProfiler2::ITEM_NOP:
12326 {
12327 // ignore
12328 break;
12329 }
12330 case CProfiler2::ITEM_SYNC:
12331 {
12332 debug_warn("Aggregated regions should not be used across frames");
12333 // still try to act sane
12334 readPos += sizeof(double);
12335 readPos += sizeof(CProfiler2::RESYNC_MAGIC);
12336 break;
12337 }
12338 case CProfiler2::ITEM_EVENT:
12339 {
12340 // skip for now
12341 readPos += sizeof(CProfiler2::SItem_dt_id);
12342 break;
12343 }
12344 case CProfiler2::ITEM_ENTER:
12345 {
12346 enter++;
12347 CProfiler2::SItem_dt_id item;
12348 memcpy(&item, buffer + readPos, sizeof(item));
12349 readPos += sizeof(item);
12350 last_time_stack.push_back((double)item.dt);
12351 last_names.push_back(item.id);
12352 current_attribute = "";
12353 break;
12354 }
12355 case CProfiler2::ITEM_LEAVE:
12356 {
12357 float item_time;
12358 memcpy(&item_time, buffer + readPos, sizeof(float));
12359 readPos += sizeof(float);
12360
12361 leaves++;
12362 if (last_names.empty())
12363 {
12364 // we somehow lost the first entry in the process
12365 debug_warn("Invalid buffer for condensing");
12366 }
12367 const char* item_name = last_names.back();
12368 last_names.pop_back();
12369
12370 if (last_time_stack.empty())
12371 {
12372 // this is the leave for the whole scope
12373 total_time = (double)item_time;
12374 break;
12375 }
12376 double time = (double)item_time - last_time_stack.back();
12377
12378 std::string name = std::string(item_name);
12379 timeByTypeMap::iterator TimeForType = timeByType.find(name);
12380 if (TimeForType == timeByType.end())
12381 {
12382 // keep reference to the original char pointer to make sure we don't break things down the line
12383 std::get<0>(timeByType[name]) = item_name;
12384 std::get<1>(timeByType[name]) = 0;
12385 }
12386 std::get<1>(timeByType[name]) += time;
12387
12388 last_time_stack.pop_back();
12389 // if we were nested, subtract our time from the below scope by making it look like it starts later
12390 if (!last_time_stack.empty())
12391 last_time_stack.back() += time;
12392
12393 if (!current_attribute.empty())
12394 {
12395 time_per_attribute[current_attribute] += time;
12396 }
12397
12398 break;
12399 }
12400 case CProfiler2::ITEM_ATTRIBUTE:
12401 {
12402 // skip for now
12403 u32 len;
12404 memcpy(&len, buffer + readPos, sizeof(len));
12405 ENSURE(len <= CProfiler2::MAX_ATTRIBUTE_LENGTH);
12406 readPos += sizeof(len);
12407
12408 char message[CProfiler2::MAX_ATTRIBUTE_LENGTH] = {0};
12409 memcpy(&message[0], buffer + readPos, std::min(size_t(len), CProfiler2::MAX_ATTRIBUTE_LENGTH));
12410 CStr mess = CStr((const char*)message, len);
12411 if (!last_names.empty())
12412 {
12413 timeByTypeMap::iterator it = timeByType.find(std::string(last_names.back()));
12414 if (it == timeByType.end())
12415 topLevelArgs.insert(mess);
12416 else
12417 std::get<2>(timeByType[std::string(last_names.back())]).insert(mess);
12418 }
12419 readPos += len;
12420 current_attribute = mess;
12421 break;
12422 }
12423 default:
12424 debug_warn(L"Invalid profiler item when condensing buffer");
12425 continue;
12426 }
12427 }
12428
12429 // rewrite the buffer
12430 // what we rewrite will always be smaller than the current buffer's size
12431 u32 writePos = 0;
12432 double curTime = initialTime;
12433 // the region enter
12434 {
12435 CProfiler2::SItem_dt_id item = { (float)curTime, regionName };
12436 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
12437 memcpy(buffer + writePos + 1, &item, sizeof(item));
12438 writePos += sizeof(item) + 1;
12439 // add a nanosecond for sanity
12440 curTime += 0.000001;
12441 }
12442 // sub-events, aggregated
12443 for (const std::pair<std::string, infoPerType>& type : timeByType)
12444 {
12445 CProfiler2::SItem_dt_id item = { (float)curTime, std::get<0>(type.second) };
12446 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
12447 memcpy(buffer + writePos + 1, &item, sizeof(item));
12448 writePos += sizeof(item) + 1;
12449
12450 // write relevant attributes if present
12451 for (const std::string& attrib : std::get<2>(type.second))
12452 {
12453 buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE;
12454 writePos++;
12455 std::string basic = attrib;
12456 std::map<std::string, double>::iterator time_attrib = time_per_attribute.find(attrib);
12457 if (time_attrib != time_per_attribute.end())
12458 basic += " " + CStr::FromInt(1000000*time_attrib->second) + "us";
12459
12460 u32 length = basic.size();
12461 memcpy(buffer + writePos, &length, sizeof(length));
12462 writePos += sizeof(length);
12463 memcpy(buffer + writePos, basic.c_str(), length);
12464 writePos += length;
12465 }
12466
12467 curTime += std::get<1>(type.second);
12468
12469 float leave_time = (float)curTime;
12470 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
12471 memcpy(buffer + writePos + 1, &leave_time, sizeof(float));
12472 writePos += sizeof(float) + 1;
12473 }
12474 // Time of computation
12475 {
12476 CProfiler2::SItem_dt_id item = { (float)curTime, "CondenseBuffer" };
12477 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER;
12478 memcpy(buffer + writePos + 1, &item, sizeof(item));
12479 writePos += sizeof(item) + 1;
12480 }
12481 {
12482 float time_out = (float)(curTime + timer_Time() - startTime);
12483 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
12484 memcpy(buffer + writePos + 1, &time_out, sizeof(float));
12485 writePos += sizeof(float) + 1;
12486 // add a nanosecond for sanity
12487 curTime += 0.000001;
12488 }
12489
12490 // the region leave
12491 {
12492 if (total_time < 0)
12493 {
12494 total_time = curTime + 0.000001;
12495
12496 buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE;
12497 writePos++;
12498 u32 length = sizeof("buffer overflow");
12499 memcpy(buffer + writePos, &length, sizeof(length));
12500 writePos += sizeof(length);
12501 memcpy(buffer + writePos, "buffer overflow", length);
12502 writePos += length;
12503 }
12504 else if (total_time < curTime)
12505 {
12506 // this seems to happen on rare occasions.
12507 curTime = total_time;
12508 }
12509 float leave_time = (float)total_time;
12510 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE;
12511 memcpy(buffer + writePos + 1, &leave_time, sizeof(float));
12512 writePos += sizeof(float) + 1;
12513 }
12514 bufferSize = writePos;
12515 }
12516
12517 void CProfiler2::ThreadStorage::HoldToBuffer(bool condensed)
12518 {
12519 ENSURE(m_HeldDepth);
12520 if (condensed)
12521 {
12522 // rewrite the buffer to show aggregated data
12523 rewriteBuffer(m_HoldBuffers[m_HeldDepth - 1].buffer, m_HoldBuffers[m_HeldDepth - 1].pos);
12524 }
12525
12526 if (m_HeldDepth > 1)
12527 {
12528 // copy onto buffer below
12529 HoldBuffer& copied = m_HoldBuffers[m_HeldDepth - 1];
12530 HoldBuffer& target = m_HoldBuffers[m_HeldDepth - 2];
12531 if (target.pos + copied.pos > HOLD_BUFFER_SIZE)
12532 return; // too much data, too bad
12533
12534 memcpy(&target.buffer[target.pos], copied.buffer, copied.pos);
12535
12536 target.pos += copied.pos;
12537 }
12538 else
12539 {
12540 u32 size = m_HoldBuffers[m_HeldDepth - 1].pos;
12541 u32 start = m_BufferPos0;
12542 if (start + size > BUFFER_SIZE)
12543 {
12544 m_BufferPos0 = size;
12545 COMPILER_FENCE;
12546 memset(m_Buffer + start, 0, BUFFER_SIZE - start);
12547 start = 0;
12548 }
12549 else
12550 {
12551 m_BufferPos0 = start + size;
12552 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer
12553 }
12554 memcpy(&m_Buffer[start], m_HoldBuffers[m_HeldDepth - 1].buffer, size);
12555 COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer
12556 m_BufferPos1 = start + size;
12557 }
12558 m_HeldDepth--;
12559 }
12560 void CProfiler2::ThreadStorage::ThrowawayHoldBuffer()
12561 {
12562 if (!m_HeldDepth)
12563 return;
12564 m_HeldDepth--;
12565 }
12566
12567 void CProfiler2::ConstructJSONOverview(std::ostream& stream)
12568 {
12569 TIMER(L"profile2 overview");
12570
12571 std::lock_guard<std::mutex> lock(m_Mutex);
12572
12573 stream << "{\"threads\":[";
12574 bool first_time = true;
12575 for (std::unique_ptr<ThreadStorage>& storage : m_Threads)
12576 {
12577 if (!first_time)
12578 stream << ",";
12579 stream << "{\"name\":\"" << CStr(storage->GetName()).EscapeToPrintableASCII() << "\"}";
12580 first_time = false;
12581 }
12582 stream << "]}";
12583 }
12584
12585 /**
12586 * Given a buffer and a visitor class (with functions OnEvent, OnEnter, OnLeave, OnAttribute),
12587 * calls the visitor for every item in the buffer.
12588 */
12589 template<typename V>
12590 void RunBufferVisitor(const std::string& buffer, V& visitor)
12591 {
12592 TIMER(L"profile2 visitor");
12593
12594 // The buffer doesn't necessarily start at the beginning of an item
12595 // (we just grabbed it from some arbitrary point in the middle),
12596 // so scan forwards until we find a sync marker.
12597 // (This is probably pretty inefficient.)
12598
12599 u32 realStart = (u32)-1; // the start point decided by the scan algorithm
12600
12601 for (u32 start = 0; start + 1 + sizeof(CProfiler2::RESYNC_MAGIC) <= buffer.length(); ++start)
12602 {
12603 if (buffer[start] == CProfiler2::ITEM_SYNC
12604 && memcmp(buffer.c_str() + start + 1, &CProfiler2::RESYNC_MAGIC, sizeof(CProfiler2::RESYNC_MAGIC)) == 0)
12605 {
12606 realStart = start;
12607 break;
12608 }
12609 }
12610
12611 ENSURE(realStart != (u32)-1); // we should have found a sync point somewhere in the buffer
12612
12613 u32 pos = realStart; // the position as we step through the buffer
12614
12615 double lastTime = -1;
12616 // set to non-negative by EVENT_SYNC; we ignore all items before that
12617 // since we can't compute their absolute times
12618
12619 while (pos < buffer.length())
12620 {
12621 u8 type = buffer[pos];
12622 ++pos;
12623
12624 switch (type)
12625 {
12626 case CProfiler2::ITEM_NOP:
12627 {
12628 // ignore
12629 break;
12630 }
12631 case CProfiler2::ITEM_SYNC:
12632 {
12633 u8 magic[sizeof(CProfiler2::RESYNC_MAGIC)];
12634 double t;
12635 memcpy(magic, buffer.c_str()+pos, ARRAY_SIZE(magic));
12636 ENSURE(memcmp(magic, &CProfiler2::RESYNC_MAGIC, sizeof(CProfiler2::RESYNC_MAGIC)) == 0);
12637 pos += sizeof(CProfiler2::RESYNC_MAGIC);
12638 memcpy(&t, buffer.c_str()+pos, sizeof(t));
12639 pos += sizeof(t);
12640 lastTime = t;
12641 visitor.OnSync(lastTime);
12642 break;
12643 }
12644 case CProfiler2::ITEM_EVENT:
12645 {
12646 CProfiler2::SItem_dt_id item;
12647 memcpy(&item, buffer.c_str()+pos, sizeof(item));
12648 pos += sizeof(item);
12649 if (lastTime >= 0)
12650 {
12651 visitor.OnEvent(lastTime + (double)item.dt, item.id);
12652 }
12653 break;
12654 }
12655 case CProfiler2::ITEM_ENTER:
12656 {
12657 CProfiler2::SItem_dt_id item;
12658 memcpy(&item, buffer.c_str()+pos, sizeof(item));
12659 pos += sizeof(item);
12660 if (lastTime >= 0)
12661 {
12662 visitor.OnEnter(lastTime + (double)item.dt, item.id);
12663 }
12664 break;
12665 }
12666 case CProfiler2::ITEM_LEAVE:
12667 {
12668 float leave_time;
12669 memcpy(&leave_time, buffer.c_str() + pos, sizeof(float));
12670 pos += sizeof(float);
12671 if (lastTime >= 0)
12672 {
12673 visitor.OnLeave(lastTime + (double)leave_time);
12674 }
12675 break;
12676 }
12677 case CProfiler2::ITEM_ATTRIBUTE:
12678 {
12679 u32 len;
12680 memcpy(&len, buffer.c_str()+pos, sizeof(len));
12681 ENSURE(len <= CProfiler2::MAX_ATTRIBUTE_LENGTH);
12682 pos += sizeof(len);
12683 std::string attribute(buffer.c_str()+pos, buffer.c_str()+pos+len);
12684 pos += len;
12685 if (lastTime >= 0)
12686 {
12687 visitor.OnAttribute(attribute);
12688 }
12689 break;
12690 }
12691 default:
12692 debug_warn(L"Invalid profiler item when parsing buffer");
12693 return;
12694 }
12695 }
12696 };
12697
12698 /**
12699 * Visitor class that dumps events as JSON.
12700 * TODO: this is pretty inefficient (in implementation and in output format).
12701 */
12702 struct BufferVisitor_Dump
12703 {
12704 NONCOPYABLE(BufferVisitor_Dump);
12705 public:
12706 BufferVisitor_Dump(std::ostream& stream) : m_Stream(stream)
12707 {
12708 }
12709
12710 void OnSync(double UNUSED(time))
12711 {
12712 // Split the array of items into an array of array (arbitrarily splitting
12713 // around the sync points) to avoid array-too-large errors in JSON decoders
12714 m_Stream << "null], [\n";
12715 }
12716
12717 void OnEvent(double time, const char* id)
12718 {
12719 m_Stream << "[1," << std::fixed << std::setprecision(9) << time;
12720 m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n";
12721 }
12722
12723 void OnEnter(double time, const char* id)
12724 {
12725 m_Stream << "[2," << std::fixed << std::setprecision(9) << time;
12726 m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n";
12727 }
12728
12729 void OnLeave(double time)
12730 {
12731 m_Stream << "[3," << std::fixed << std::setprecision(9) << time << "],\n";
12732 }
12733
12734 void OnAttribute(const std::string& attr)
12735 {
12736 m_Stream << "[4,\"" << CStr(attr).EscapeToPrintableASCII() << "\"],\n";
12737 }
12738
12739 std::ostream& m_Stream;
12740 };
12741
12742 const char* CProfiler2::ConstructJSONResponse(std::ostream& stream, const std::string& thread)
12743 {
12744 TIMER(L"profile2 query");
12745
12746 std::string buffer;
12747
12748 {
12749 TIMER(L"profile2 get buffer");
12750
12751 std::lock_guard<std::mutex> lock(m_Mutex); // lock against changes to m_Threads or deletions of ThreadStorage
12752
12753 std::vector<std::unique_ptr<ThreadStorage>>::iterator it =
12754 std::find_if(m_Threads.begin(), m_Threads.end(), [&thread](std::unique_ptr<ThreadStorage>& storage) {
12755 return storage->GetName() == thread;
12756 });
12757
12758 if (it == m_Threads.end())
12759 return "cannot find named thread";
12760
12761 stream << "{\"events\":[\n";
12762
12763 stream << "[\n";
12764 buffer = (*it)->GetBuffer();
12765 }
12766
12767 BufferVisitor_Dump visitor(stream);
12768 RunBufferVisitor(buffer, visitor);
12769
12770 stream << "null]\n]}";
12771
12772 return NULL;
12773 }
12774
12775 void CProfiler2::SaveToFile()
12776 {
12777 OsPath path = psLogDir()/"profile2.jsonp";
12778 std::ofstream stream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
12779 ENSURE(stream.good());
12780
12781 stream << "profileDataCB({\"threads\": [\n";
12782 bool first_time = true;
12783 for (std::unique_ptr<ThreadStorage>& storage : m_Threads)
12784 {
12785 if (!first_time)
12786 stream << ",\n";
12787 stream << "{\"name\":\"" << CStr(storage->GetName()).EscapeToPrintableASCII() << "\",\n";
12788 stream << "\"data\": ";
12789 ConstructJSONResponse(stream, storage->GetName());
12790 stream << "\n}";
12791 first_time = false;
12792 }
12793 stream << "\n]});\n";
12794 }
12795
12796 CProfile2SpikeRegion::CProfile2SpikeRegion(const char* name, double spikeLimit) :
12797 m_Name(name), m_Limit(spikeLimit), m_PushedHold(true)
12798 {
12799 if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE)
12800 g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_SPIKE);
12801 else
12802 m_PushedHold = false;
12803 COMPILER_FENCE;
12804 g_Profiler2.RecordRegionEnter(m_Name);
12805 m_StartTime = g_Profiler2.GetTime();
12806 }
12807 CProfile2SpikeRegion::~CProfile2SpikeRegion()
12808 {
12809 double time = g_Profiler2.GetTime();
12810 g_Profiler2.RecordRegionLeave();
12811 bool shouldWrite = time - m_StartTime > m_Limit;
12812
12813 if (m_PushedHold)
12814 g_Profiler2.StopHoldingMessages(shouldWrite);
12815 }
12816
12817 CProfile2AggregatedRegion::CProfile2AggregatedRegion(const char* name, double spikeLimit) :
12818 m_Name(name), m_Limit(spikeLimit), m_PushedHold(true)
12819 {
12820 if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE)
12821 g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_AGGREGATE);
12822 else
12823 m_PushedHold = false;
12824 COMPILER_FENCE;
12825 g_Profiler2.RecordRegionEnter(m_Name);
12826 m_StartTime = g_Profiler2.GetTime();
12827 }
12828 CProfile2AggregatedRegion::~CProfile2AggregatedRegion()
12829 {
12830 double time = g_Profiler2.GetTime();
12831 g_Profiler2.RecordRegionLeave();
12832 bool shouldWrite = time - m_StartTime > m_Limit;
12833
12834 if (m_PushedHold)
12835 g_Profiler2.StopHoldingMessages(shouldWrite, true);
12836 }
12837Index: source/ps/Replay.h
12838===================================================================
12839--- source/ps/Replay.h (revision 23275)
12840+++ source/ps/Replay.h (working copy)
12841@@ -1,118 +1,119 @@
12842 /* Copyright (C) 2019 Wildfire Games.
12843 * This file is part of 0 A.D.
12844 *
12845 * 0 A.D. is free software: you can redistribute it and/or modify
12846 * it under the terms of the GNU General Public License as published by
12847 * the Free Software Foundation, either version 2 of the License, or
12848 * (at your option) any later version.
12849 *
12850 * 0 A.D. is distributed in the hope that it will be useful,
12851 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12852 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12853 * GNU General Public License for more details.
12854 *
12855 * You should have received a copy of the GNU General Public License
12856 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
12857 */
12858
12859 #ifndef INCLUDED_REPLAY
12860 #define INCLUDED_REPLAY
12861
12862+#include "lib/os_path.h"
12863 #include "ps/CStr.h"
12864 #include "scriptinterface/ScriptTypes.h"
12865
12866 struct SimulationCommand;
12867 class CSimulation2;
12868 class ScriptInterface;
12869
12870 /**
12871 * Replay log recorder interface.
12872 * Call its methods at appropriate times during the game.
12873 */
12874 class IReplayLogger
12875 {
12876 public:
12877 IReplayLogger() { }
12878 virtual ~IReplayLogger() { }
12879
12880 /**
12881 * Started the game with the given game attributes.
12882 */
12883 virtual void StartGame(JS::MutableHandleValue attribs) = 0;
12884
12885 /**
12886 * Run the given turn with the given collection of player commands.
12887 */
12888 virtual void Turn(u32 n, u32 turnLength, std::vector<SimulationCommand>& commands) = 0;
12889
12890 /**
12891 * Optional hash of simulation state (for sync checking).
12892 */
12893 virtual void Hash(const std::string& hash, bool quick) = 0;
12894
12895 /**
12896 * Saves metadata.json containing part of the simulation state used for the summary screen.
12897 */
12898 virtual void SaveMetadata(const CSimulation2& simulation) = 0;
12899
12900 /**
12901 * Remember the directory containing the commands.txt file, so that we can save additional files to it.
12902 */
12903 virtual OsPath GetDirectory() const = 0;
12904 };
12905
12906 /**
12907 * Implementation of IReplayLogger that simply throws away all data.
12908 */
12909 class CDummyReplayLogger : public IReplayLogger
12910 {
12911 public:
12912 virtual void StartGame(JS::MutableHandleValue UNUSED(attribs)) { }
12913 virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), std::vector<SimulationCommand>& UNUSED(commands)) { }
12914 virtual void Hash(const std::string& UNUSED(hash), bool UNUSED(quick)) { }
12915 virtual void SaveMetadata(const CSimulation2& UNUSED(simulation)) { };
12916 virtual OsPath GetDirectory() const { return OsPath(); }
12917 };
12918
12919 /**
12920 * Implementation of IReplayLogger that saves data to a file in the logs directory.
12921 */
12922 class CReplayLogger : public IReplayLogger
12923 {
12924 NONCOPYABLE(CReplayLogger);
12925 public:
12926 CReplayLogger(const ScriptInterface& scriptInterface);
12927 ~CReplayLogger();
12928
12929 virtual void StartGame(JS::MutableHandleValue attribs);
12930 virtual void Turn(u32 n, u32 turnLength, std::vector<SimulationCommand>& commands);
12931 virtual void Hash(const std::string& hash, bool quick);
12932 virtual void SaveMetadata(const CSimulation2& simulation);
12933 virtual OsPath GetDirectory() const;
12934
12935 private:
12936 const ScriptInterface& m_ScriptInterface;
12937 std::ostream* m_Stream;
12938 OsPath m_Directory;
12939 };
12940
12941 /**
12942 * Replay log replayer. Runs the log with no graphics and dumps some info to stdout.
12943 */
12944 class CReplayPlayer
12945 {
12946 public:
12947 CReplayPlayer();
12948 ~CReplayPlayer();
12949
12950 void Load(const OsPath& path);
12951 void Replay(const bool serializationtest, const int rejointestturn, const bool ooslog, const bool testHashFull, const bool testHashQuick);
12952
12953 private:
12954 std::istream* m_Stream;
12955 CStr ModListToString(const std::vector<std::vector<CStr>>& list) const;
12956 void CheckReplayMods(const ScriptInterface& scriptInterface, JS::HandleValue attribs) const;
12957 void TestHash(const std::string& hashType, const std::string& replayHash, const bool testHashFull, const bool testHashQuick);
12958 };
12959
12960 #endif // INCLUDED_REPLAY
12961Index: source/ps/UserReport.cpp
12962===================================================================
12963--- source/ps/UserReport.cpp (revision 23275)
12964+++ source/ps/UserReport.cpp (working copy)
12965@@ -1,636 +1,637 @@
12966 /* Copyright (C) 2019 Wildfire Games.
12967 * This file is part of 0 A.D.
12968 *
12969 * 0 A.D. is free software: you can redistribute it and/or modify
12970 * it under the terms of the GNU General Public License as published by
12971 * the Free Software Foundation, either version 2 of the License, or
12972 * (at your option) any later version.
12973 *
12974 * 0 A.D. is distributed in the hope that it will be useful,
12975 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12976 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12977 * GNU General Public License for more details.
12978 *
12979 * You should have received a copy of the GNU General Public License
12980 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
12981 */
12982
12983 #include "precompiled.h"
12984
12985 #include "UserReport.h"
12986
12987 #include "lib/timer.h"
12988 #include "lib/utf8.h"
12989 #include "lib/external_libraries/curl.h"
12990 #include "lib/external_libraries/zlib.h"
12991 #include "lib/file/archive/stream.h"
12992 #include "lib/os_path.h"
12993 #include "lib/sysdep/sysdep.h"
12994 #include "ps/ConfigDB.h"
12995 #include "ps/Filesystem.h"
12996 #include "ps/Profiler2.h"
12997+#include "ps/Pyrogenesis.h"
12998
12999 #include <condition_variable>
13000 #include <fstream>
13001 #include <mutex>
13002 #include <string>
13003 #include <thread>
13004
13005 #define DEBUG_UPLOADS 0
13006
13007 /*
13008 * The basic idea is that the game submits reports to us, which we send over
13009 * HTTP to a server for storage and analysis.
13010 *
13011 * We can't use libcurl's asynchronous 'multi' API, because DNS resolution can
13012 * be synchronous and slow (which would make the game pause).
13013 * So we use the 'easy' API in a background thread.
13014 * The main thread submits reports, toggles whether uploading is enabled,
13015 * and polls for the current status (typically to display in the GUI);
13016 * the worker thread does all of the uploading.
13017 *
13018 * It'd be nice to extend this in the future to handle things like crash reports.
13019 * The game should store the crashlogs (suitably anonymised) in a directory, and
13020 * we should detect those files and upload them when we're restarted and online.
13021 */
13022
13023
13024 /**
13025 * Version number stored in config file when the user agrees to the reporting.
13026 * Reporting will be disabled if the config value is missing or is less than
13027 * this value. If we start reporting a lot more data, we should increase this
13028 * value and get the user to re-confirm.
13029 */
13030 static const int REPORTER_VERSION = 1;
13031
13032 /**
13033 * Time interval (seconds) at which the worker thread will check its reconnection
13034 * timers. (This should be relatively high so the thread doesn't waste much time
13035 * continually waking up.)
13036 */
13037 static const double TIMER_CHECK_INTERVAL = 10.0;
13038
13039 /**
13040 * Seconds we should wait before reconnecting to the server after a failure.
13041 */
13042 static const double RECONNECT_INVERVAL = 60.0;
13043
13044 CUserReporter g_UserReporter;
13045
13046 struct CUserReport
13047 {
13048 time_t m_Time;
13049 std::string m_Type;
13050 int m_Version;
13051 std::string m_Data;
13052 };
13053
13054 class CUserReporterWorker
13055 {
13056 public:
13057 CUserReporterWorker(const std::string& userID, const std::string& url) :
13058 m_URL(url), m_UserID(userID), m_Enabled(false), m_Shutdown(false), m_Status("disabled"),
13059 m_PauseUntilTime(timer_Time()), m_LastUpdateTime(timer_Time())
13060 {
13061 // Set up libcurl:
13062
13063 m_Curl = curl_easy_init();
13064 ENSURE(m_Curl);
13065
13066 #if DEBUG_UPLOADS
13067 curl_easy_setopt(m_Curl, CURLOPT_VERBOSE, 1L);
13068 #endif
13069
13070 // Capture error messages
13071 curl_easy_setopt(m_Curl, CURLOPT_ERRORBUFFER, m_ErrorBuffer);
13072
13073 // Disable signal handlers (required for multithreaded applications)
13074 curl_easy_setopt(m_Curl, CURLOPT_NOSIGNAL, 1L);
13075
13076 // To minimise security risks, don't support redirects
13077 curl_easy_setopt(m_Curl, CURLOPT_FOLLOWLOCATION, 0L);
13078
13079 // Prevent this thread from blocking the engine shutdown for 5 minutes in case the server is unavailable
13080 curl_easy_setopt(m_Curl, CURLOPT_CONNECTTIMEOUT, 10L);
13081
13082 // Set IO callbacks
13083 curl_easy_setopt(m_Curl, CURLOPT_WRITEFUNCTION, ReceiveCallback);
13084 curl_easy_setopt(m_Curl, CURLOPT_WRITEDATA, this);
13085 curl_easy_setopt(m_Curl, CURLOPT_READFUNCTION, SendCallback);
13086 curl_easy_setopt(m_Curl, CURLOPT_READDATA, this);
13087
13088 // Set URL to POST to
13089 curl_easy_setopt(m_Curl, CURLOPT_URL, url.c_str());
13090 curl_easy_setopt(m_Curl, CURLOPT_POST, 1L);
13091
13092 // Set up HTTP headers
13093 m_Headers = NULL;
13094 // Set the UA string
13095 std::string ua = "User-Agent: 0ad ";
13096 ua += curl_version();
13097 ua += " (http://play0ad.com/)";
13098 m_Headers = curl_slist_append(m_Headers, ua.c_str());
13099 // Override the default application/x-www-form-urlencoded type since we're not using that type
13100 m_Headers = curl_slist_append(m_Headers, "Content-Type: application/octet-stream");
13101 // Disable the Accept header because it's a waste of a dozen bytes
13102 m_Headers = curl_slist_append(m_Headers, "Accept: ");
13103 curl_easy_setopt(m_Curl, CURLOPT_HTTPHEADER, m_Headers);
13104
13105 m_WorkerThread = std::thread(RunThread, this);
13106 }
13107
13108 ~CUserReporterWorker()
13109 {
13110 curl_slist_free_all(m_Headers);
13111 curl_easy_cleanup(m_Curl);
13112 }
13113
13114 /**
13115 * Called by main thread, when the online reporting is enabled/disabled.
13116 */
13117 void SetEnabled(bool enabled)
13118 {
13119 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13120 if (enabled != m_Enabled)
13121 {
13122 m_Enabled = enabled;
13123
13124 // Wake up the worker thread
13125 m_WorkerCV.notify_all();
13126 }
13127 }
13128
13129 /**
13130 * Called by main thread to request shutdown.
13131 * Returns true if we've shut down successfully.
13132 * Returns false if shutdown is taking too long (we might be blocked on a
13133 * sync network operation) - you mustn't destroy this object, just leak it
13134 * and terminate.
13135 */
13136 bool Shutdown()
13137 {
13138 {
13139 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13140 m_Shutdown = true;
13141 }
13142
13143 // Wake up the worker thread
13144 m_WorkerCV.notify_all();
13145
13146 // Wait for it to shut down cleanly
13147 // TODO: should have a timeout in case of network hangs
13148 m_WorkerThread.join();
13149
13150 return true;
13151 }
13152
13153 /**
13154 * Called by main thread to determine the current status of the uploader.
13155 */
13156 std::string GetStatus()
13157 {
13158 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13159 return m_Status;
13160 }
13161
13162 /**
13163 * Called by main thread to add a new report to the queue.
13164 */
13165 void Submit(const shared_ptr<CUserReport>& report)
13166 {
13167 {
13168 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13169 m_ReportQueue.push_back(report);
13170 }
13171
13172 // Wake up the worker thread
13173 m_WorkerCV.notify_all();
13174 }
13175
13176 /**
13177 * Called by the main thread every frame, so we can check
13178 * retransmission timers.
13179 */
13180 void Update()
13181 {
13182 double now = timer_Time();
13183 if (now > m_LastUpdateTime + TIMER_CHECK_INTERVAL)
13184 {
13185 // Wake up the worker thread
13186 m_WorkerCV.notify_all();
13187
13188 m_LastUpdateTime = now;
13189 }
13190 }
13191
13192 private:
13193 static void RunThread(CUserReporterWorker* data)
13194 {
13195 debug_SetThreadName("CUserReportWorker");
13196 g_Profiler2.RegisterCurrentThread("userreport");
13197
13198 data->Run();
13199 }
13200
13201 void Run()
13202 {
13203 // Set libcurl's proxy configuration
13204 // (This has to be done in the thread because it's potentially very slow)
13205 SetStatus("proxy");
13206 std::wstring proxy;
13207
13208 {
13209 PROFILE2("get proxy config");
13210 if (sys_get_proxy_config(wstring_from_utf8(m_URL), proxy) == INFO::OK)
13211 curl_easy_setopt(m_Curl, CURLOPT_PROXY, utf8_from_wstring(proxy).c_str());
13212 }
13213
13214 SetStatus("waiting");
13215
13216 /*
13217 * We use a condition_variable to let the thread be woken up when it has
13218 * work to do. Various actions from the main thread can wake it:
13219 * * SetEnabled()
13220 * * Shutdown()
13221 * * Submit()
13222 * * Retransmission timeouts, once every several seconds
13223 *
13224 * If multiple actions have triggered wakeups, we might respond to
13225 * all of those actions after the first wakeup, which is okay (we'll do
13226 * nothing during the subsequent wakeups). We should never hang due to
13227 * processing fewer actions than wakeups.
13228 *
13229 * Retransmission timeouts are triggered via the main thread.
13230 */
13231
13232 // Wait until the main thread wakes us up
13233 while (true)
13234 {
13235 g_Profiler2.RecordRegionEnter("condition_variable wait");
13236
13237 std::unique_lock<std::mutex> lock(m_WorkerMutex);
13238 m_WorkerCV.wait(lock);
13239 lock.unlock();
13240
13241 g_Profiler2.RecordRegionLeave();
13242
13243 // Handle shutdown requests as soon as possible
13244 if (GetShutdown())
13245 return;
13246
13247 // If we're not enabled, ignore this wakeup
13248 if (!GetEnabled())
13249 continue;
13250
13251 // If we're still pausing due to a failed connection,
13252 // go back to sleep again
13253 if (timer_Time() < m_PauseUntilTime)
13254 continue;
13255
13256 // We're enabled, so process as many reports as possible
13257 while (ProcessReport())
13258 {
13259 // Handle shutdowns while we were sending the report
13260 if (GetShutdown())
13261 return;
13262 }
13263 }
13264 }
13265
13266 bool GetEnabled()
13267 {
13268 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13269 return m_Enabled;
13270 }
13271
13272 bool GetShutdown()
13273 {
13274 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13275 return m_Shutdown;
13276 }
13277
13278 void SetStatus(const std::string& status)
13279 {
13280 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13281 m_Status = status;
13282 #if DEBUG_UPLOADS
13283 debug_printf(">>> CUserReporterWorker status: %s\n", status.c_str());
13284 #endif
13285 }
13286
13287 bool ProcessReport()
13288 {
13289 PROFILE2("process report");
13290
13291 shared_ptr<CUserReport> report;
13292
13293 {
13294 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13295 if (m_ReportQueue.empty())
13296 return false;
13297 report = m_ReportQueue.front();
13298 m_ReportQueue.pop_front();
13299 }
13300
13301 ConstructRequestData(*report);
13302 m_RequestDataOffset = 0;
13303 m_ResponseData.clear();
13304 m_ErrorBuffer[0] = '\0';
13305
13306 curl_easy_setopt(m_Curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)m_RequestData.size());
13307
13308 SetStatus("connecting");
13309
13310 #if DEBUG_UPLOADS
13311 TIMER(L"CUserReporterWorker request");
13312 #endif
13313
13314 CURLcode err = curl_easy_perform(m_Curl);
13315
13316 #if DEBUG_UPLOADS
13317 printf(">>>\n%s\n<<<\n", m_ResponseData.c_str());
13318 #endif
13319
13320 if (err == CURLE_OK)
13321 {
13322 long code = -1;
13323 curl_easy_getinfo(m_Curl, CURLINFO_RESPONSE_CODE, &code);
13324 SetStatus("completed:" + CStr::FromInt(code));
13325
13326 // Check for success code
13327 if (code == 200)
13328 return true;
13329
13330 // If the server returns the 410 Gone status, interpret that as meaning
13331 // it no longer supports uploads (at least from this version of the game),
13332 // so shut down and stop talking to it (to avoid wasting bandwidth)
13333 if (code == 410)
13334 {
13335 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13336 m_Shutdown = true;
13337 return false;
13338 }
13339 }
13340 else
13341 {
13342 std::string errorString(m_ErrorBuffer);
13343
13344 if (errorString.empty())
13345 errorString = curl_easy_strerror(err);
13346
13347 SetStatus("failed:" + CStr::FromInt(err) + ":" + errorString);
13348 }
13349
13350 // We got an unhandled return code or a connection failure;
13351 // push this report back onto the queue and try again after
13352 // a long interval
13353
13354 {
13355 std::lock_guard<std::mutex> lock(m_WorkerMutex);
13356 m_ReportQueue.push_front(report);
13357 }
13358
13359 m_PauseUntilTime = timer_Time() + RECONNECT_INVERVAL;
13360 return false;
13361 }
13362
13363 void ConstructRequestData(const CUserReport& report)
13364 {
13365 // Construct the POST request data in the application/x-www-form-urlencoded format
13366
13367 std::string r;
13368
13369 r += "user_id=";
13370 AppendEscaped(r, m_UserID);
13371
13372 r += "&time=" + CStr::FromInt64(report.m_Time);
13373
13374 r += "&type=";
13375 AppendEscaped(r, report.m_Type);
13376
13377 r += "&version=" + CStr::FromInt(report.m_Version);
13378
13379 r += "&data=";
13380 AppendEscaped(r, report.m_Data);
13381
13382 // Compress the content with zlib to save bandwidth.
13383 // (Note that we send a request with unlabelled compressed data instead
13384 // of using Content-Encoding, because Content-Encoding is a mess and causes
13385 // problems with servers and breaks Content-Length and this is much easier.)
13386 std::string compressed;
13387 compressed.resize(compressBound(r.size()));
13388 uLongf destLen = compressed.size();
13389 int ok = compress((Bytef*)compressed.c_str(), &destLen, (const Bytef*)r.c_str(), r.size());
13390 ENSURE(ok == Z_OK);
13391 compressed.resize(destLen);
13392
13393 m_RequestData.swap(compressed);
13394 }
13395
13396 void AppendEscaped(std::string& buffer, const std::string& str)
13397 {
13398 char* escaped = curl_easy_escape(m_Curl, str.c_str(), str.size());
13399 buffer += escaped;
13400 curl_free(escaped);
13401 }
13402
13403 static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp)
13404 {
13405 CUserReporterWorker* self = static_cast<CUserReporterWorker*>(userp);
13406
13407 if (self->GetShutdown())
13408 return 0; // signals an error
13409
13410 self->m_ResponseData += std::string((char*)buffer, (char*)buffer+size*nmemb);
13411
13412 return size*nmemb;
13413 }
13414
13415 static size_t SendCallback(char* bufptr, size_t size, size_t nmemb, void* userp)
13416 {
13417 CUserReporterWorker* self = static_cast<CUserReporterWorker*>(userp);
13418
13419 if (self->GetShutdown())
13420 return CURL_READFUNC_ABORT; // signals an error
13421
13422 // We can return as much data as available, up to the buffer size
13423 size_t amount = std::min(self->m_RequestData.size() - self->m_RequestDataOffset, size*nmemb);
13424
13425 // ...But restrict to sending a small amount at once, so that we remain
13426 // responsive to shutdown requests even if the network is pretty slow
13427 amount = std::min((size_t)1024, amount);
13428
13429 if(amount != 0) // (avoids invalid operator[] call where index=size)
13430 {
13431 memcpy(bufptr, &self->m_RequestData[self->m_RequestDataOffset], amount);
13432 self->m_RequestDataOffset += amount;
13433 }
13434
13435 self->SetStatus("sending:" + CStr::FromDouble((double)self->m_RequestDataOffset / self->m_RequestData.size()));
13436
13437 return amount;
13438 }
13439
13440 private:
13441 // Thread-related members:
13442 std::thread m_WorkerThread;
13443 std::mutex m_WorkerMutex;
13444 std::condition_variable m_WorkerCV;
13445
13446 // Shared by main thread and worker thread:
13447 // These variables are all protected by m_WorkerMutex
13448 std::deque<shared_ptr<CUserReport> > m_ReportQueue;
13449 bool m_Enabled;
13450 bool m_Shutdown;
13451 std::string m_Status;
13452
13453 // Initialised in constructor by main thread; otherwise used only by worker thread:
13454 std::string m_URL;
13455 std::string m_UserID;
13456 CURL* m_Curl;
13457 curl_slist* m_Headers;
13458 double m_PauseUntilTime;
13459
13460 // Only used by worker thread:
13461 std::string m_ResponseData;
13462 std::string m_RequestData;
13463 size_t m_RequestDataOffset;
13464 char m_ErrorBuffer[CURL_ERROR_SIZE];
13465
13466 // Only used by main thread:
13467 double m_LastUpdateTime;
13468 };
13469
13470
13471
13472 CUserReporter::CUserReporter() :
13473 m_Worker(NULL)
13474 {
13475 }
13476
13477 CUserReporter::~CUserReporter()
13478 {
13479 ENSURE(!m_Worker); // Deinitialize should have been called before shutdown
13480 }
13481
13482 std::string CUserReporter::LoadUserID()
13483 {
13484 std::string userID;
13485
13486 // Read the user ID from user.cfg (if there is one)
13487 CFG_GET_VAL("userreport.id", userID);
13488
13489 // If we don't have a validly-formatted user ID, generate a new one
13490 if (userID.length() != 16)
13491 {
13492 u8 bytes[8] = {0};
13493 sys_generate_random_bytes(bytes, ARRAY_SIZE(bytes));
13494 // ignore failures - there's not much we can do about it
13495
13496 userID = "";
13497 for (size_t i = 0; i < ARRAY_SIZE(bytes); ++i)
13498 {
13499 char hex[3];
13500 sprintf_s(hex, ARRAY_SIZE(hex), "%02x", (unsigned int)bytes[i]);
13501 userID += hex;
13502 }
13503
13504 g_ConfigDB.SetValueString(CFG_USER, "userreport.id", userID);
13505 g_ConfigDB.WriteValueToFile(CFG_USER, "userreport.id", userID);
13506 }
13507
13508 return userID;
13509 }
13510
13511 bool CUserReporter::IsReportingEnabled()
13512 {
13513 int version = -1;
13514 CFG_GET_VAL("userreport.enabledversion", version);
13515 return (version >= REPORTER_VERSION);
13516 }
13517
13518 void CUserReporter::SetReportingEnabled(bool enabled)
13519 {
13520 CStr val = CStr::FromInt(enabled ? REPORTER_VERSION : 0);
13521 g_ConfigDB.SetValueString(CFG_USER, "userreport.enabledversion", val);
13522 g_ConfigDB.WriteValueToFile(CFG_USER, "userreport.enabledversion", val);
13523
13524 if (m_Worker)
13525 m_Worker->SetEnabled(enabled);
13526 }
13527
13528 std::string CUserReporter::GetStatus()
13529 {
13530 if (!m_Worker)
13531 return "disabled";
13532
13533 return m_Worker->GetStatus();
13534 }
13535
13536 void CUserReporter::Initialize()
13537 {
13538 ENSURE(!m_Worker); // must only be called once
13539
13540 std::string userID = LoadUserID();
13541 std::string url;
13542 CFG_GET_VAL("userreport.url_upload", url);
13543
13544 m_Worker = new CUserReporterWorker(userID, url);
13545
13546 m_Worker->SetEnabled(IsReportingEnabled());
13547 }
13548
13549 void CUserReporter::Deinitialize()
13550 {
13551 if (!m_Worker)
13552 return;
13553
13554 if (m_Worker->Shutdown())
13555 {
13556 // Worker was shut down cleanly
13557 SAFE_DELETE(m_Worker);
13558 }
13559 else
13560 {
13561 // Worker failed to shut down in a reasonable time
13562 // Leak the resources (since that's better than hanging or crashing)
13563 m_Worker = NULL;
13564 }
13565 }
13566
13567 void CUserReporter::Update()
13568 {
13569 if (m_Worker)
13570 m_Worker->Update();
13571 }
13572
13573 void CUserReporter::SubmitReport(const std::string& type, int version, const std::string& data, const std::string& dataHumanReadable)
13574 {
13575 // Write to logfile, enabling users to assess privacy concerns before the data is submitted
13576 if (!dataHumanReadable.empty())
13577 {
13578 OsPath path = psLogDir() / OsPath("userreport_" + type + ".txt");
13579 std::ofstream stream(OsString(path), std::ofstream::trunc);
13580 if (stream)
13581 {
13582 debug_printf("UserReport written to %s\n", path.string8().c_str());
13583 stream << dataHumanReadable << std::endl;
13584 stream.close();
13585 }
13586 else
13587 debug_printf("Failed to write UserReport to %s\n", path.string8().c_str());
13588 }
13589
13590 // If not initialised, discard the report
13591 if (!m_Worker)
13592 return;
13593
13594 // Actual submit
13595 shared_ptr<CUserReport> report(new CUserReport);
13596 report->m_Time = time(NULL);
13597 report->m_Type = type;
13598 report->m_Version = version;
13599 report->m_Data = data;
13600
13601 m_Worker->Submit(report);
13602 }
13603Index: source/ps/Util.cpp
13604===================================================================
13605--- source/ps/Util.cpp (revision 23275)
13606+++ source/ps/Util.cpp (working copy)
13607@@ -1,451 +1,458 @@
13608 /* Copyright (C) 2019 Wildfire Games.
13609 * This file is part of 0 A.D.
13610 *
13611 * 0 A.D. is free software: you can redistribute it and/or modify
13612 * it under the terms of the GNU General Public License as published by
13613 * the Free Software Foundation, either version 2 of the License, or
13614 * (at your option) any later version.
13615 *
13616 * 0 A.D. is distributed in the hope that it will be useful,
13617 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13618 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13619 * GNU General Public License for more details.
13620 *
13621 * You should have received a copy of the GNU General Public License
13622 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
13623 */
13624
13625 #include "precompiled.h"
13626
13627 #include "ps/Util.h"
13628
13629 #include "lib/posix/posix_utsname.h"
13630 #include "lib/ogl.h"
13631 #include "lib/snd.h"
13632 #include "lib/timer.h"
13633 #include "lib/bits.h" // round_up
13634 #include "lib/allocators/shared_ptr.h"
13635 #include "lib/sysdep/sysdep.h" // sys_OpenFile
13636 #include "lib/sysdep/gfx.h"
13637 #include "lib/sysdep/cpu.h"
13638 #include "lib/sysdep/os_cpu.h"
13639 #if ARCH_X86_X64
13640 #include "lib/sysdep/arch/x86_x64/topology.h"
13641 #endif
13642 #include "lib/sysdep/smbios.h"
13643 #include "lib/tex/tex.h"
13644
13645 #include "i18n/L10n.h"
13646 #include "lib/utf8.h"
13647
13648 #include "ps/GameSetup/Config.h"
13649 #include "ps/GameSetup/GameSetup.h"
13650 #include "ps/Game.h"
13651 #include "ps/CLogger.h"
13652 #include "ps/Filesystem.h"
13653+#include "ps/Pyrogenesis.h"
13654 #include "ps/VideoMode.h"
13655 #include "renderer/Renderer.h"
13656 #include "maths/MathUtil.h"
13657 #include "graphics/GameView.h"
13658
13659 #include <iomanip>
13660 #include <sstream>
13661
13662 extern CStrW g_CursorName;
13663
13664 static std::string SplitExts(const char *exts)
13665 {
13666 std::string str = exts;
13667 std::string ret = "";
13668 size_t idx = str.find_first_of(" ");
13669 while(idx != std::string::npos)
13670 {
13671 if(idx >= str.length() - 1)
13672 {
13673 ret += str;
13674 break;
13675 }
13676
13677 ret += str.substr(0, idx);
13678 ret += "\n";
13679 str = str.substr(idx + 1);
13680 idx = str.find_first_of(" ");
13681 }
13682
13683 return ret;
13684 }
13685
13686
13687 void WriteSystemInfo()
13688 {
13689 TIMER(L"write_sys_info");
13690
13691+#if CONFIG2_AUDIO
13692 // get_cpu_info and gfx_detect already called during init - see call site
13693 snd_detect();
13694+#endif
13695
13696 struct utsname un;
13697 uname(&un);
13698
13699 OsPath pathname = psLogDir()/"system_info.txt";
13700 FILE* f = sys_OpenFile(pathname, "w");
13701 if(!f)
13702 return;
13703
13704 // current timestamp (redundant WRT OS timestamp, but that is not
13705 // visible when people are posting this file's contents online)
13706 {
13707 wchar_t timestampBuf[100] = {'\0'};
13708 time_t seconds;
13709 time(&seconds);
13710 struct tm* t = gmtime(&seconds);
13711 const size_t charsWritten = wcsftime(timestampBuf, ARRAY_SIZE(timestampBuf), L"(generated %Y-%m-%d %H:%M:%S UTC)", t);
13712 ENSURE(charsWritten != 0);
13713 fprintf(f, "%ls\n\n", timestampBuf);
13714 }
13715
13716 // OS
13717 fprintf(f, "OS : %s %s (%s)\n", un.sysname, un.release, un.version);
13718
13719 // CPU
13720 fprintf(f, "CPU : %s, %s", un.machine, cpu_IdentifierString());
13721 #if ARCH_X86_X64
13722 fprintf(f, " (%dx%dx%d)", (int)topology::NumPackages(), (int)topology::CoresPerPackage(), (int)topology::LogicalPerCore());
13723 #endif
13724 double cpuClock = os_cpu_ClockFrequency(); // query OS (may fail)
13725 #if ARCH_X86_X64
13726 if(cpuClock <= 0.0)
13727 cpuClock = x86_x64::ClockFrequency(); // measure (takes a few ms)
13728 #endif
13729 if(cpuClock > 0.0)
13730 {
13731 if(cpuClock < 1e9)
13732 fprintf(f, ", %.2f MHz\n", cpuClock*1e-6);
13733 else
13734 fprintf(f, ", %.2f GHz\n", cpuClock*1e-9);
13735 }
13736 else
13737 fprintf(f, "\n");
13738
13739 // memory
13740 fprintf(f, "Memory : %u MiB; %u MiB free\n", (unsigned)os_cpu_MemorySize(), (unsigned)os_cpu_MemoryAvailable());
13741
13742 // graphics
13743 const std::wstring cardName = gfx::CardName();
13744 const std::wstring driverInfo = gfx::DriverInfo();
13745 fprintf(f, "Graphics Card : %ls\n", cardName.c_str());
13746 fprintf(f, "OpenGL Drivers : %s; %ls\n", glGetString(GL_VERSION), driverInfo.c_str());
13747 fprintf(f, "Video Mode : %dx%d:%d\n", g_VideoMode.GetXRes(), g_VideoMode.GetYRes(), g_VideoMode.GetBPP());
13748
13749 // sound
13750+#if CONFIG2_AUDIO
13751 fprintf(f, "Sound Card : %s\n", snd_card.c_str());
13752 fprintf(f, "Sound Drivers : %s\n", snd_drv_ver.c_str());
13753+#else
13754+ fprintf(f, "Sound : disabled\n");
13755+#endif
13756
13757 // OpenGL extensions (write them last, since it's a lot of text)
13758 const char* exts = ogl_ExtensionString();
13759 if (!exts) exts = "{unknown}";
13760 fprintf(f, "\nOpenGL Extensions: \n%s\n", SplitExts(exts).c_str());
13761
13762 // System Management BIOS (even more text than OpenGL extensions)
13763 std::string smbios = SMBIOS::StringizeStructures(SMBIOS::GetStructures());
13764 fprintf(f, "\nSMBIOS: \n%s\n", smbios.c_str());
13765
13766 fclose(f);
13767 f = 0;
13768 }
13769
13770
13771 // not thread-safe!
13772 static const wchar_t* HardcodedErrorString(int err)
13773 {
13774 static wchar_t description[200];
13775 StatusDescription((Status)err, description, ARRAY_SIZE(description));
13776 return description;
13777 }
13778
13779 // not thread-safe!
13780 const wchar_t* ErrorString(int err)
13781 {
13782 // language file not available (yet)
13783 return HardcodedErrorString(err);
13784
13785 // TODO: load from language file
13786 }
13787
13788
13789
13790 // write the specified texture to disk.
13791 // note: <t> cannot be made const because the image may have to be
13792 // transformed to write it out in the format determined by <fn>'s extension.
13793 Status tex_write(Tex* t, const VfsPath& filename)
13794 {
13795 DynArray da;
13796 RETURN_STATUS_IF_ERR(t->encode(filename.Extension(), &da));
13797
13798 // write to disk
13799 Status ret = INFO::OK;
13800 {
13801 shared_ptr<u8> file = DummySharedPtr(da.base);
13802 const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
13803 if(bytes_written > 0)
13804 ENSURE(bytes_written == (ssize_t)da.pos);
13805 else
13806 ret = (Status)bytes_written;
13807 }
13808
13809 (void)da_free(&da);
13810 return ret;
13811 }
13812
13813 /**
13814 * Return an unused directory, based on date and index (for example 2016-02-09_0001)
13815 */
13816 OsPath createDateIndexSubdirectory(const OsPath& parentDir)
13817 {
13818 const std::time_t timestamp = std::time(nullptr);
13819 const struct std::tm* now = std::localtime(×tamp);
13820
13821 // Two processes executing this simultaneously might attempt to create the same directory.
13822 int tries = 0;
13823 const int maxTries = 10;
13824
13825 int i = 0;
13826 OsPath path;
13827 char directory[256];
13828
13829 do
13830 {
13831 sprintf(directory, "%04d-%02d-%02d_%04d", now->tm_year+1900, now->tm_mon+1, now->tm_mday, ++i);
13832 path = parentDir / CStr(directory);
13833
13834 if (DirectoryExists(path) || FileExists(path))
13835 continue;
13836
13837 if (CreateDirectories(path, 0700, ++tries > maxTries) == INFO::OK)
13838 break;
13839
13840 } while(tries <= maxTries);
13841
13842 return path;
13843 }
13844
13845 static size_t s_nextScreenshotNumber;
13846
13847 // <extension> identifies the file format that is to be written
13848 // (case-insensitive). examples: "bmp", "png", "jpg".
13849 // BMP is good for quick output at the expense of large files.
13850 void WriteScreenshot(const VfsPath& extension)
13851 {
13852 // get next available numbered filename
13853 // note: %04d -> always 4 digits, so sorting by filename works correctly.
13854 const VfsPath basenameFormat(L"screenshots/screenshot%04d");
13855 const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension);
13856 VfsPath filename;
13857 vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename);
13858
13859 const size_t w = (size_t)g_xres, h = (size_t)g_yres;
13860 const size_t bpp = 24;
13861 GLenum fmt = GL_RGB;
13862 int flags = TEX_BOTTOM_UP;
13863 // we want writing BMP to be as fast as possible,
13864 // so read data from OpenGL in BMP format to obviate conversion.
13865 if(extension == L".bmp")
13866 {
13867 #if !CONFIG2_GLES // GLES doesn't support BGR
13868 fmt = GL_BGR;
13869 flags |= TEX_BGR;
13870 #endif
13871 }
13872
13873 // Hide log messages and re-render
13874 RenderLogger(false);
13875 Render();
13876 RenderLogger(true);
13877
13878 const size_t img_size = w * h * bpp/8;
13879 const size_t hdr_size = tex_hdr_size(filename);
13880 shared_ptr<u8> buf;
13881 AllocateAligned(buf, hdr_size+img_size, maxSectorSize);
13882 GLvoid* img = buf.get() + hdr_size;
13883 Tex t;
13884 if(t.wrap(w, h, bpp, flags, buf, hdr_size) < 0)
13885 return;
13886 glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img);
13887
13888 if (tex_write(&t, filename) == INFO::OK)
13889 {
13890 OsPath realPath;
13891 g_VFS->GetRealPath(filename, realPath);
13892
13893 LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
13894
13895 debug_printf(
13896 CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
13897 realPath.string8().c_str());
13898 }
13899 else
13900 LOGERROR("Error writing screenshot to '%s'", filename.string8());
13901 }
13902
13903
13904
13905 // Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles.
13906 void WriteBigScreenshot(const VfsPath& extension, int tiles)
13907 {
13908 // If the game hasn't started yet then use WriteScreenshot to generate the image.
13909 if(g_Game == NULL){ WriteScreenshot(L".bmp"); return; }
13910
13911 // get next available numbered filename
13912 // note: %04d -> always 4 digits, so sorting by filename works correctly.
13913 const VfsPath basenameFormat(L"screenshots/screenshot%04d");
13914 const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension);
13915 VfsPath filename;
13916 vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename);
13917
13918 // Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and
13919 // hope the screen is actually large enough for that.
13920 const int tile_w = 640, tile_h = 480;
13921 ENSURE(g_xres >= tile_w && g_yres >= tile_h);
13922
13923 const int img_w = tile_w*tiles, img_h = tile_h*tiles;
13924 const int bpp = 24;
13925 GLenum fmt = GL_RGB;
13926 int flags = TEX_BOTTOM_UP;
13927 // we want writing BMP to be as fast as possible,
13928 // so read data from OpenGL in BMP format to obviate conversion.
13929 if(extension == L".bmp")
13930 {
13931 #if !CONFIG2_GLES // GLES doesn't support BGR
13932 fmt = GL_BGR;
13933 flags |= TEX_BGR;
13934 #endif
13935 }
13936
13937 const size_t img_size = img_w * img_h * bpp/8;
13938 const size_t tile_size = tile_w * tile_h * bpp/8;
13939 const size_t hdr_size = tex_hdr_size(filename);
13940 void* tile_data = malloc(tile_size);
13941 if(!tile_data)
13942 {
13943 WARN_IF_ERR(ERR::NO_MEM);
13944 return;
13945 }
13946 shared_ptr<u8> img_buf;
13947 AllocateAligned(img_buf, hdr_size+img_size, maxSectorSize);
13948
13949 Tex t;
13950 GLvoid* img = img_buf.get() + hdr_size;
13951 if(t.wrap(img_w, img_h, bpp, flags, img_buf, hdr_size) < 0)
13952 {
13953 free(tile_data);
13954 return;
13955 }
13956
13957 ogl_WarnIfError();
13958
13959 CCamera oldCamera = *g_Game->GetView()->GetCamera();
13960
13961 // Resize various things so that the sizes and aspect ratios are correct
13962 {
13963 g_Renderer.Resize(tile_w, tile_h);
13964 SViewPort vp = { 0, 0, tile_w, tile_h };
13965 g_Game->GetView()->SetViewport(vp);
13966 }
13967
13968 #if !CONFIG2_GLES
13969 // Temporarily move everything onto the front buffer, so the user can
13970 // see the exciting progress as it renders (and can tell when it's finished).
13971 // (It doesn't just use SwapBuffers, because it doesn't know whether to
13972 // call the SDL version or the Atlas version.)
13973 GLint oldReadBuffer, oldDrawBuffer;
13974 glGetIntegerv(GL_READ_BUFFER, &oldReadBuffer);
13975 glGetIntegerv(GL_DRAW_BUFFER, &oldDrawBuffer);
13976 glDrawBuffer(GL_FRONT);
13977 glReadBuffer(GL_FRONT);
13978 #endif
13979
13980 // Hide the cursor
13981 CStrW oldCursor = g_CursorName;
13982 g_CursorName = L"";
13983
13984 // Render each tile
13985 CMatrix3D projection;
13986 projection.SetIdentity();
13987 for (int tile_y = 0; tile_y < tiles; ++tile_y)
13988 {
13989 for (int tile_x = 0; tile_x < tiles; ++tile_x)
13990 {
13991 // Adjust the camera to render the appropriate region
13992 if (oldCamera.GetProjectionType() == CCamera::PERSPECTIVE)
13993 {
13994 projection.SetPerspectiveTile(oldCamera.GetFOV(), oldCamera.GetAspectRatio(), oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tile_x, tile_y);
13995 }
13996 g_Game->GetView()->GetCamera()->SetProjection(projection);
13997
13998 RenderLogger(false);
13999 RenderGui(false);
14000 Render();
14001 RenderGui(true);
14002 RenderLogger(true);
14003
14004 // Copy the tile pixels into the main image
14005 glReadPixels(0, 0, tile_w, tile_h, fmt, GL_UNSIGNED_BYTE, tile_data);
14006 for (int y = 0; y < tile_h; ++y)
14007 {
14008 void* dest = (char*)img + ((tile_y*tile_h + y) * img_w + (tile_x*tile_w)) * bpp/8;
14009 void* src = (char*)tile_data + y * tile_w * bpp/8;
14010 memcpy(dest, src, tile_w * bpp/8);
14011 }
14012 }
14013 }
14014
14015 // Restore the old cursor
14016 g_CursorName = oldCursor;
14017
14018 #if !CONFIG2_GLES
14019 // Restore the buffer settings
14020 glDrawBuffer(oldDrawBuffer);
14021 glReadBuffer(oldReadBuffer);
14022 #endif
14023
14024 // Restore the viewport settings
14025 {
14026 g_Renderer.Resize(g_xres, g_yres);
14027 SViewPort vp = { 0, 0, g_xres, g_yres };
14028 g_Game->GetView()->SetViewport(vp);
14029 g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera);
14030 }
14031
14032 if (tex_write(&t, filename) == INFO::OK)
14033 {
14034 OsPath realPath;
14035 g_VFS->GetRealPath(filename, realPath);
14036
14037 LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
14038
14039 debug_printf(
14040 CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
14041 realPath.string8().c_str());
14042 }
14043 else
14044 LOGERROR("Error writing screenshot to '%s'", filename.string8());
14045
14046 free(tile_data);
14047 }
14048
14049 std::string Hexify(const std::string& s)
14050 {
14051 std::stringstream str;
14052 str << std::hex;
14053 for (const char& c : s)
14054 str << std::setfill('0') << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c));
14055 return str.str();
14056 }
14057
14058 std::string Hexify(const u8* s, size_t length)
14059 {
14060 std::stringstream str;
14061 str << std::hex;
14062 for (size_t i = 0; i < length; ++i)
14063 str << std::setfill('0') << std::setw(2) << static_cast<int>(s[i]);
14064 return str.str();
14065 }
14066Index: source/ps/VideoMode.cpp
14067===================================================================
14068--- source/ps/VideoMode.cpp (revision 23275)
14069+++ source/ps/VideoMode.cpp (working copy)
14070@@ -1,535 +1,544 @@
14071 /* Copyright (C) 2018 Wildfire Games.
14072 * This file is part of 0 A.D.
14073 *
14074 * 0 A.D. is free software: you can redistribute it and/or modify
14075 * it under the terms of the GNU General Public License as published by
14076 * the Free Software Foundation, either version 2 of the License, or
14077 * (at your option) any later version.
14078 *
14079 * 0 A.D. is distributed in the hope that it will be useful,
14080 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14081 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14082 * GNU General Public License for more details.
14083 *
14084 * You should have received a copy of the GNU General Public License
14085 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
14086 */
14087
14088 #include "precompiled.h"
14089
14090 #include "VideoMode.h"
14091
14092 #include "graphics/Camera.h"
14093 #include "graphics/GameView.h"
14094 #include "gui/GUIManager.h"
14095 #include "lib/config2.h"
14096 #include "lib/ogl.h"
14097 #include "lib/external_libraries/libsdl.h"
14098 #include "lib/sysdep/gfx.h"
14099 #include "lib/tex/tex.h"
14100 #include "ps/CConsole.h"
14101 #include "ps/CLogger.h"
14102 #include "ps/ConfigDB.h"
14103 #include "ps/Filesystem.h"
14104 #include "ps/Game.h"
14105 #include "ps/GameSetup/Config.h"
14106 #include "renderer/Renderer.h"
14107
14108 #if OS_MACOSX
14109 # include "lib/sysdep/os/osx/osx_sys_version.h"
14110 #endif
14111
14112
14113 static int DEFAULT_WINDOW_W = 1024;
14114 static int DEFAULT_WINDOW_H = 768;
14115
14116 static int DEFAULT_FULLSCREEN_W = 1024;
14117 static int DEFAULT_FULLSCREEN_H = 768;
14118
14119 CVideoMode g_VideoMode;
14120
14121 CVideoMode::CVideoMode() :
14122 m_IsFullscreen(false), m_IsInitialised(false), m_Window(NULL),
14123 m_PreferredW(0), m_PreferredH(0), m_PreferredBPP(0), m_PreferredFreq(0),
14124 m_ConfigW(0), m_ConfigH(0), m_ConfigBPP(0), m_ConfigFullscreen(false), m_ConfigForceS3TCEnable(true),
14125 m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0)
14126 {
14127 // (m_ConfigFullscreen defaults to false, so users don't get stuck if
14128 // e.g. half the filesystem is missing and the config files aren't loaded)
14129 }
14130
14131 void CVideoMode::ReadConfig()
14132 {
14133 bool windowed = !m_ConfigFullscreen;
14134 CFG_GET_VAL("windowed", windowed);
14135 m_ConfigFullscreen = !windowed;
14136
14137 CFG_GET_VAL("xres", m_ConfigW);
14138 CFG_GET_VAL("yres", m_ConfigH);
14139 CFG_GET_VAL("bpp", m_ConfigBPP);
14140 CFG_GET_VAL("display", m_ConfigDisplay);
14141 CFG_GET_VAL("force_s3tc_enable", m_ConfigForceS3TCEnable);
14142 }
14143
14144 bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
14145 {
14146 Uint32 flags = 0;
14147 if (fullscreen)
14148 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
14149
14150 if (!m_Window)
14151 {
14152 // Note: these flags only take affect in SDL_CreateWindow
14153 flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
14154 m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay);
14155
14156 m_Window = SDL_CreateWindow("0 A.D.", m_WindowedX, m_WindowedY, w, h, flags);
14157 if (!m_Window)
14158 {
14159 // If fullscreen fails, try windowed mode
14160 if (fullscreen)
14161 {
14162 LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution "
14163 "%dx%d:%d (\"%hs\"), falling back to windowed mode",
14164 w, h, bpp, SDL_GetError());
14165 // Using default size for the window for now, as the attempted setting
14166 // could be as large, or larger than the screen size.
14167 return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false);
14168 }
14169 else
14170 {
14171 LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")",
14172 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
14173 return false;
14174 }
14175 }
14176
14177 if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0)
14178 {
14179 LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")",
14180 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
14181 return false;
14182 }
14183
14184 SDL_GLContext context = SDL_GL_CreateContext(m_Window);
14185 if (!context)
14186 {
14187 LOGERROR("SetVideoMode failed in SDL_GL_CreateContext: %dx%d:%d %d (\"%s\")",
14188 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
14189 return false;
14190 }
14191 }
14192 else
14193 {
14194 if (m_IsFullscreen != fullscreen)
14195 {
14196 if (!fullscreen)
14197 {
14198 // For some reason, when switching from fullscreen to windowed mode,
14199 // we have to set the window size and position before and after switching
14200 SDL_SetWindowSize(m_Window, w, h);
14201 SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
14202 }
14203
14204 if (SDL_SetWindowFullscreen(m_Window, flags) < 0)
14205 {
14206 LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")",
14207 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
14208 return false;
14209 }
14210 }
14211
14212 if (!fullscreen)
14213 {
14214 SDL_SetWindowSize(m_Window, w, h);
14215 SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
14216 }
14217 }
14218
14219 // Grab the current video settings
14220 SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH);
14221 m_CurrentBPP = bpp;
14222
14223 if (fullscreen)
14224 SDL_SetWindowGrab(m_Window, SDL_TRUE);
14225 else
14226 SDL_SetWindowGrab(m_Window, SDL_FALSE);
14227
14228 m_IsFullscreen = fullscreen;
14229
14230 g_xres = m_CurrentW;
14231 g_yres = m_CurrentH;
14232
14233 return true;
14234 }
14235
14236 bool CVideoMode::InitSDL()
14237 {
14238+ SDL_DisplayMode mode;
14239+
14240 ENSURE(!m_IsInitialised);
14241
14242 ReadConfig();
14243
14244 EnableS3TC();
14245
14246 // preferred video mode = current desktop settings
14247 // (command line params may override these)
14248- gfx::GetVideoMode(&m_PreferredW, &m_PreferredH, &m_PreferredBPP, &m_PreferredFreq);
14249+ // TODO: handle multi-screen properly
14250+ if (SDL_GetDesktopDisplayMode(0, &mode) == 0)
14251+ {
14252+ m_PreferredW = mode.w;
14253+ m_PreferredH = mode.h;
14254+ m_PreferredBPP = SDL_BITSPERPIXEL(mode.format);
14255+ m_PreferredFreq = mode.refresh_rate;
14256+ }
14257
14258 int w = m_ConfigW;
14259 int h = m_ConfigH;
14260
14261 if (m_ConfigFullscreen)
14262 {
14263 // If fullscreen and no explicit size set, default to the desktop resolution
14264 if (w == 0 || h == 0)
14265 {
14266 w = m_PreferredW;
14267 h = m_PreferredH;
14268 }
14269 }
14270
14271 // If no size determined, default to something sensible
14272 if (w == 0 || h == 0)
14273 {
14274 w = DEFAULT_WINDOW_W;
14275 h = DEFAULT_WINDOW_H;
14276 }
14277
14278 if (!m_ConfigFullscreen)
14279 {
14280 // Limit the window to the screen size (if known)
14281 if (m_PreferredW)
14282 w = std::min(w, m_PreferredW);
14283 if (m_PreferredH)
14284 h = std::min(h, m_PreferredH);
14285 }
14286
14287 int bpp = GetBestBPP();
14288
14289 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
14290 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
14291 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
14292
14293 #if CONFIG2_GLES
14294 // Require GLES 2.0
14295 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
14296 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
14297 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
14298 #endif
14299
14300 if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
14301 {
14302 // Fall back to a smaller depth buffer
14303 // (The rendering may be ugly but this helps when running in VMware)
14304 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
14305
14306 if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
14307 return false;
14308 }
14309
14310 SDL_GL_SetSwapInterval(g_VSync ? 1 : 0);
14311
14312 // Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13).
14313 // The driver appears to register its own atexit hook on context creation.
14314 // If this atexit hook is called before SDL_Quit destroys the OpenGL context,
14315 // some kind of double-free problem causes a crash and lockup in the driver.
14316 // Calling SDL_Quit twice appears to be harmless, though, and avoids the problem
14317 // by destroying the context *before* the driver's atexit hook is called.
14318 // (Note that atexit hooks are guaranteed to be called in reverse order of their registration.)
14319 atexit(SDL_Quit);
14320 // End work around.
14321
14322 ogl_Init(); // required after each mode change
14323 // (TODO: does that mean we need to call this when toggling fullscreen later?)
14324
14325 #if !OS_ANDROID
14326 u16 ramp[256];
14327 SDL_CalculateGammaRamp(g_Gamma, ramp);
14328 if (SDL_SetWindowGammaRamp(m_Window, ramp, ramp, ramp) < 0)
14329 LOGWARNING("SDL_SetWindowGammaRamp failed");
14330 #endif
14331
14332 m_IsInitialised = true;
14333
14334 if (!m_ConfigFullscreen)
14335 {
14336 m_WindowedW = w;
14337 m_WindowedH = h;
14338 }
14339
14340 SetWindowIcon();
14341
14342 return true;
14343 }
14344
14345 bool CVideoMode::InitNonSDL()
14346 {
14347 ENSURE(!m_IsInitialised);
14348
14349 ReadConfig();
14350
14351 EnableS3TC();
14352
14353 m_IsInitialised = true;
14354
14355 return true;
14356 }
14357
14358 void CVideoMode::Shutdown()
14359 {
14360 ENSURE(m_IsInitialised);
14361
14362 m_IsFullscreen = false;
14363 m_IsInitialised = false;
14364 if (m_Window)
14365 {
14366 SDL_DestroyWindow(m_Window);
14367 m_Window = NULL;
14368 }
14369 }
14370
14371 void CVideoMode::EnableS3TC()
14372 {
14373 // On Linux we have to try hard to get S3TC compressed texture support.
14374 // If the extension is already provided by default, that's fine.
14375 // Otherwise we should enable the 'force_s3tc_enable' environment variable
14376 // and (re)initialise the video system, so that Mesa provides the extension
14377 // (if the driver at least supports decompression).
14378 // (This overrides the force_s3tc_enable specified via driconf files.)
14379 // Otherwise we should complain to the user, and stop using compressed textures.
14380 //
14381 // Setting the environment variable causes Mesa to print an ugly message to stderr
14382 // ("ATTENTION: default value of option force_s3tc_enable overridden by environment."),
14383 // so it'd be nicer to skip that if S3TC will be supported by default,
14384 // but reinitialising video is a pain (and it might do weird things when fullscreen)
14385 // so we just unconditionally set it (unless our config file explicitly disables it).
14386
14387 #if !(OS_WIN || OS_MACOSX) // (assume Mesa is used for all non-Windows non-Mac platforms)
14388 if (m_ConfigForceS3TCEnable)
14389 setenv("force_s3tc_enable", "true", 0);
14390 #endif
14391 }
14392
14393 bool CVideoMode::ResizeWindow(int w, int h)
14394 {
14395 ENSURE(m_IsInitialised);
14396
14397 // Ignore if not windowed
14398 if (m_IsFullscreen)
14399 return true;
14400
14401 // Ignore if the size hasn't changed
14402 if (w == m_WindowedW && h == m_WindowedH)
14403 return true;
14404
14405 int bpp = GetBestBPP();
14406
14407 if (!SetVideoMode(w, h, bpp, false))
14408 return false;
14409
14410 m_WindowedW = w;
14411 m_WindowedH = h;
14412
14413 UpdateRenderer(w, h);
14414
14415 return true;
14416 }
14417
14418 bool CVideoMode::SetFullscreen(bool fullscreen)
14419 {
14420 // This might get called before initialisation by psDisplayError;
14421 // if so then silently fail
14422 if (!m_IsInitialised)
14423 return false;
14424
14425 // Check whether this is actually a change
14426 if (fullscreen == m_IsFullscreen)
14427 return true;
14428
14429 if (!m_IsFullscreen)
14430 {
14431 // Windowed -> fullscreen:
14432
14433 int w = 0, h = 0;
14434
14435 // If a fullscreen size was configured, use that; else use the desktop size; else use a default
14436 if (m_ConfigFullscreen)
14437 {
14438 w = m_ConfigW;
14439 h = m_ConfigH;
14440 }
14441 if (w == 0 || h == 0)
14442 {
14443 w = m_PreferredW;
14444 h = m_PreferredH;
14445 }
14446 if (w == 0 || h == 0)
14447 {
14448 w = DEFAULT_FULLSCREEN_W;
14449 h = DEFAULT_FULLSCREEN_H;
14450 }
14451
14452 int bpp = GetBestBPP();
14453
14454 if (!SetVideoMode(w, h, bpp, fullscreen))
14455 return false;
14456
14457 UpdateRenderer(m_CurrentW, m_CurrentH);
14458
14459 return true;
14460 }
14461 else
14462 {
14463 // Fullscreen -> windowed:
14464
14465 // Go back to whatever the previous window size was
14466 int w = m_WindowedW, h = m_WindowedH;
14467
14468 int bpp = GetBestBPP();
14469
14470 if (!SetVideoMode(w, h, bpp, fullscreen))
14471 return false;
14472
14473 UpdateRenderer(w, h);
14474
14475 return true;
14476 }
14477 }
14478
14479 bool CVideoMode::ToggleFullscreen()
14480 {
14481 return SetFullscreen(!m_IsFullscreen);
14482 }
14483
14484 bool CVideoMode::IsInFullscreen() const
14485 {
14486 return m_IsFullscreen;
14487 }
14488
14489 void CVideoMode::UpdatePosition(int x, int y)
14490 {
14491 if (!m_IsFullscreen)
14492 {
14493 m_WindowedX = x;
14494 m_WindowedY = y;
14495 }
14496 }
14497
14498 void CVideoMode::UpdateRenderer(int w, int h)
14499 {
14500 if (w < 2) w = 2; // avoid GL errors caused by invalid sizes
14501 if (h < 2) h = 2;
14502
14503 g_xres = w;
14504 g_yres = h;
14505
14506 SViewPort vp = { 0, 0, w, h };
14507
14508 if (CRenderer::IsInitialised())
14509 {
14510 g_Renderer.SetViewport(vp);
14511 g_Renderer.Resize(w, h);
14512 }
14513
14514 if (g_GUI)
14515 g_GUI->UpdateResolution();
14516
14517 if (g_Console)
14518 g_Console->UpdateScreenSize(w, h);
14519
14520 if (g_Game)
14521 g_Game->GetView()->SetViewport(vp);
14522 }
14523
14524 int CVideoMode::GetBestBPP()
14525 {
14526 if (m_ConfigBPP)
14527 return m_ConfigBPP;
14528 if (m_PreferredBPP)
14529 return m_PreferredBPP;
14530 return 32;
14531 }
14532
14533 int CVideoMode::GetXRes()
14534 {
14535 ENSURE(m_IsInitialised);
14536 return m_CurrentW;
14537 }
14538
14539 int CVideoMode::GetYRes()
14540 {
14541 ENSURE(m_IsInitialised);
14542 return m_CurrentH;
14543 }
14544
14545 int CVideoMode::GetBPP()
14546 {
14547 ENSURE(m_IsInitialised);
14548 return m_CurrentBPP;
14549 }
14550
14551 int CVideoMode::GetDesktopXRes()
14552 {
14553 ENSURE(m_IsInitialised);
14554 return m_PreferredW;
14555 }
14556
14557 int CVideoMode::GetDesktopYRes()
14558 {
14559 ENSURE(m_IsInitialised);
14560 return m_PreferredH;
14561 }
14562
14563 int CVideoMode::GetDesktopBPP()
14564 {
14565 ENSURE(m_IsInitialised);
14566 return m_PreferredBPP;
14567 }
14568
14569 int CVideoMode::GetDesktopFreq()
14570 {
14571 ENSURE(m_IsInitialised);
14572 return m_PreferredFreq;
14573 }
14574
14575 SDL_Window* CVideoMode::GetWindow()
14576 {
14577 ENSURE(m_IsInitialised);
14578 return m_Window;
14579 }
14580
14581 void CVideoMode::SetWindowIcon()
14582 {
14583 // The window icon should be kept outside of art/textures/, or else it will be converted
14584 // to DDS by the archive builder and will become unusable here. Using DDS makes BGRA
14585 // conversion needlessly complicated.
14586 std::shared_ptr<u8> iconFile;
14587 size_t iconFileSize;
14588 if (g_VFS->LoadFile("art/icons/window.png", iconFile, iconFileSize) != INFO::OK)
14589 {
14590 LOGWARNING("Window icon not found.");
14591 return;
14592 }
14593
14594 Tex iconTexture;
14595 if (iconTexture.decode(iconFile, iconFileSize) != INFO::OK)
14596 return;
14597
14598 // Convert to required BGRA format.
14599 const size_t iconFlags = (iconTexture.m_Flags | TEX_BGR) & ~TEX_DXT;
14600 if (iconTexture.transform_to(iconFlags) != INFO::OK)
14601 return;
14602
14603 void* bgra_img = iconTexture.get_data();
14604 if (!bgra_img)
14605 return;
14606
14607 SDL_Surface *iconSurface = SDL_CreateRGBSurfaceFrom(bgra_img,
14608 iconTexture.m_Width, iconTexture.m_Height, 32, iconTexture.m_Width * 4,
14609 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
14610 if (!iconSurface)
14611 return;
14612
14613 SDL_SetWindowIcon(m_Window, iconSurface);
14614 SDL_FreeSurface(iconSurface);
14615 }
14616Index: source/ps/VideoMode.h
14617===================================================================
14618--- source/ps/VideoMode.h (revision 23275)
14619+++ source/ps/VideoMode.h (working copy)
14620@@ -1,137 +1,138 @@
14621 /* Copyright (C) 2018 Wildfire Games.
14622 * This file is part of 0 A.D.
14623 *
14624 * 0 A.D. is free software: you can redistribute it and/or modify
14625 * it under the terms of the GNU General Public License as published by
14626 * the Free Software Foundation, either version 2 of the License, or
14627 * (at your option) any later version.
14628 *
14629 * 0 A.D. is distributed in the hope that it will be useful,
14630 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14631 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14632 * GNU General Public License for more details.
14633 *
14634 * You should have received a copy of the GNU General Public License
14635 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
14636 */
14637
14638 #ifndef INCLUDED_VIDEOMODE
14639 #define INCLUDED_VIDEOMODE
14640
14641 typedef struct SDL_Window SDL_Window;
14642
14643 class CVideoMode
14644 {
14645 public:
14646 CVideoMode();
14647
14648 /**
14649 * Initialise the video mode, for use in an SDL-using application.
14650 */
14651 bool InitSDL();
14652
14653 /**
14654 * Initialise parts of the video mode, for use in Atlas (which uses
14655 * wxWidgets instead of SDL for GL).
14656 * Currently this just tries to enable S3TC.
14657 */
14658 bool InitNonSDL();
14659
14660 /**
14661 * Shut down after InitSDL/InitNonSDL, so that they can be used again.
14662 */
14663 void Shutdown();
14664
14665 /**
14666 * Resize the SDL window and associated graphics stuff to the new size.
14667 */
14668 bool ResizeWindow(int w, int h);
14669
14670 /**
14671 * Switch to fullscreen or windowed mode.
14672 */
14673 bool SetFullscreen(bool fullscreen);
14674
14675 /**
14676 * Returns true if window runs in fullscreen mode.
14677 */
14678 bool IsInFullscreen() const;
14679
14680 /**
14681 * Switch between fullscreen and windowed mode.
14682 */
14683 bool ToggleFullscreen();
14684
14685 /**
14686 * Update window position, to restore later if necessary (SDL2 only).
14687 */
14688 void UpdatePosition(int x, int y);
14689
14690 /**
14691 * Update the graphics code to start drawing to the new size.
14692 * This should be called after the GL context has been resized.
14693 * This can also be used when the GL context is managed externally, not via SDL.
14694 */
14695 static void UpdateRenderer(int w, int h);
14696
14697 int GetXRes();
14698 int GetYRes();
14699 int GetBPP();
14700
14701 int GetDesktopXRes();
14702 int GetDesktopYRes();
14703 int GetDesktopBPP();
14704 int GetDesktopFreq();
14705
14706 SDL_Window* GetWindow();
14707
14708 void SetWindowIcon();
14709
14710 private:
14711 void ReadConfig();
14712 int GetBestBPP();
14713 bool SetVideoMode(int w, int h, int bpp, bool fullscreen);
14714 void EnableS3TC();
14715
14716 /**
14717 * Remember whether Init has been called. (This isn't used for anything
14718 * important, just for verifying that the callers call our methods in
14719 * the right order.)
14720 */
14721 bool m_IsInitialised;
14722
14723 SDL_Window* m_Window;
14724
14725- // Initial desktop settings
14726+ // Initial desktop settings.
14727+ // Frequency is in Hz, and BPP actually means bits per pixels (not bytes per pixels)
14728 int m_PreferredW;
14729 int m_PreferredH;
14730 int m_PreferredBPP;
14731 int m_PreferredFreq;
14732
14733 // Config file settings (0 if unspecified)
14734 int m_ConfigW;
14735 int m_ConfigH;
14736 int m_ConfigBPP;
14737 int m_ConfigDisplay;
14738 bool m_ConfigFullscreen;
14739 bool m_ConfigForceS3TCEnable;
14740
14741 // If we're fullscreen, size/position of window when we were last windowed (or the default window
14742 // size/position if we started fullscreen), to support switching back to the old window size/position
14743 int m_WindowedW;
14744 int m_WindowedH;
14745 int m_WindowedX;
14746 int m_WindowedY;
14747
14748 // Whether we're currently being displayed fullscreen
14749 bool m_IsFullscreen;
14750
14751 // The last mode selected
14752 int m_CurrentW;
14753 int m_CurrentH;
14754 int m_CurrentBPP;
14755 };
14756
14757 extern CVideoMode g_VideoMode;
14758
14759 #endif // INCLUDED_VIDEOMODE
14760Index: source/ps/VisualReplay.h
14761===================================================================
14762--- source/ps/VisualReplay.h (revision 23275)
14763+++ source/ps/VisualReplay.h (working copy)
14764@@ -1,123 +1,125 @@
14765 /* Copyright (C) 2019 Wildfire Games.
14766 * This file is part of 0 A.D.
14767 *
14768 * 0 A.D. is free software: you can redistribute it and/or modify
14769 * it under the terms of the GNU General Public License as published by
14770 * the Free Software Foundation, either version 2 of the License, or
14771 * (at your option) any later version.
14772 *
14773 * 0 A.D. is distributed in the hope that it will be useful,
14774 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14775 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14776 * GNU General Public License for more details.
14777 *
14778 * You should have received a copy of the GNU General Public License
14779 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
14780 */
14781
14782 #ifndef INCLUDED_REPlAY
14783 #define INCLUDED_REPlAY
14784
14785 #include "scriptinterface/ScriptInterface.h"
14786+#include "lib/os_path.h"
14787+
14788 class CSimulation2;
14789 class CGUIManager;
14790
14791 /**
14792 * Contains functions for visually replaying past games.
14793 */
14794 namespace VisualReplay
14795 {
14796
14797 /**
14798 * Returns the absolute path to the sim-log directory (that contains the directories with the replay files.
14799 */
14800 OsPath GetDirectoryPath();
14801
14802 /**
14803 * Returns the absolute path to the replay cache file.
14804 */
14805 OsPath GetCacheFilePath();
14806
14807 /**
14808 * Returns the absolute path to the temporary replay cache file used to
14809 * always have a valid cache file in place even if bad things happen.
14810 */
14811 OsPath GetTempCacheFilePath();
14812
14813 /**
14814 * Replays the commands.txt file in the given subdirectory visually.
14815 */
14816 bool StartVisualReplay(const OsPath& directory);
14817
14818 /**
14819 * Reads the replay Cache file and parses it into a jsObject
14820 *
14821 * @param scriptInterface - the ScriptInterface in which to create the return data.
14822 * @param cachedReplaysObject - the cached replays.
14823 * @return true on succes
14824 */
14825 bool ReadCacheFile(const ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject);
14826
14827 /**
14828 * Stores the replay list in the replay cache file
14829 *
14830 * @param scriptInterface - the ScriptInterface in which to create the return data.
14831 * @param replays - the replay list to store.
14832 */
14833 void StoreCacheFile(const ScriptInterface& scriptInterface, JS::HandleObject replays);
14834
14835 /**
14836 * Load the replay cache and check if there are new/deleted replays. If so, update the cache.
14837 *
14838 * @param scriptInterface - the ScriptInterface in which to create the return data.
14839 * @param compareFiles - compare the directory name and the FileSize of the replays and the cache.
14840 * @return cache entries
14841 */
14842 JS::HandleObject ReloadReplayCache(const ScriptInterface& scriptInterface, bool compareFiles);
14843
14844 /**
14845 * Get a list of replays to display in the GUI.
14846 *
14847 * @param scriptInterface - the ScriptInterface in which to create the return data.
14848 * @param compareFiles - reload the cache, which takes more time,
14849 * but nearly ensures, that no changed replay is missed.
14850 * @return array of objects containing replay data
14851 */
14852 JS::Value GetReplays(const ScriptInterface& scriptInterface, bool compareFiles);
14853
14854 /**
14855 * Parses a commands.txt file and extracts metadata.
14856 * Works similarly to CGame::LoadReplayData().
14857 */
14858 JS::Value LoadReplayData(const ScriptInterface& scriptInterface, const OsPath& directory);
14859
14860 /**
14861 * Permanently deletes the visual replay (including the parent directory)
14862 *
14863 * @param replayFile - path to commands.txt, whose parent directory will be deleted.
14864 * @return true if deletion was successful, false on error
14865 */
14866 bool DeleteReplay(const OsPath& replayFile);
14867
14868 /**
14869 * Returns the parsed header of the replay file (commands.txt).
14870 */
14871 JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const OsPath& directoryName);
14872
14873 /**
14874 * Returns whether or not the metadata / summary screen data has been saved properly when the game ended.
14875 */
14876 bool HasReplayMetadata(const OsPath& directoryName);
14877
14878 /**
14879 * Returns the metadata of a replay.
14880 */
14881 JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const OsPath& directoryName);
14882
14883 /**
14884 * Adds a replay to the replayCache.
14885 */
14886 void AddReplayToCache(const ScriptInterface& scriptInterface, const CStrW& directoryName);
14887 }
14888
14889 #endif
14890Index: source/soundmanager/scripting/JSInterface_Sound.cpp
14891===================================================================
14892--- source/soundmanager/scripting/JSInterface_Sound.cpp (revision 23275)
14893+++ source/soundmanager/scripting/JSInterface_Sound.cpp (working copy)
14894@@ -1,153 +1,153 @@
14895 /* Copyright (C) 2018 Wildfire Games.
14896 * This file is part of 0 A.D.
14897 *
14898 * 0 A.D. is free software: you can redistribute it and/or modify
14899 * it under the terms of the GNU General Public License as published by
14900 * the Free Software Foundation, either version 2 of the License, or
14901 * (at your option) any later version.
14902 *
14903 * 0 A.D. is distributed in the hope that it will be useful,
14904 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14905 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14906 * GNU General Public License for more details.
14907 *
14908 * You should have received a copy of the GNU General Public License
14909 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
14910 */
14911 #include "precompiled.h"
14912
14913 #include "JSInterface_Sound.h"
14914
14915 #include "lib/config2.h"
14916 #include "lib/utf8.h"
14917 #include "maths/Vector3D.h"
14918 #include "ps/Filesystem.h"
14919 #include "scriptinterface/ScriptInterface.h"
14920 #include "soundmanager/SoundManager.h"
14921
14922 #include <sstream>
14923
14924 namespace JSI_Sound
14925 {
14926 #if CONFIG2_AUDIO
14927
14928 void StartMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
14929 {
14930 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14931 sndManager->SetMusicEnabled(true);
14932 }
14933
14934 void StopMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
14935 {
14936 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14937 sndManager->SetMusicEnabled(false);
14938 }
14939
14940 void ClearPlaylist(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
14941 {
14942 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14943 sndManager->ClearPlayListItems();
14944 }
14945
14946 void AddPlaylistItem(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename)
14947 {
14948 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14949 sndManager->AddPlayListItem(VfsPath(filename));
14950 }
14951
14952 void StartPlaylist(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool looping)
14953 {
14954 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14955 sndManager->StartPlayList(looping );
14956 }
14957
14958 void PlayMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename, bool looping)
14959 {
14960 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14961 sndManager->PlayAsMusic(filename, looping);
14962 }
14963
14964 void PlayUISound(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename, bool looping)
14965 {
14966 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14967 sndManager->PlayAsUI(filename, looping);
14968 }
14969
14970 void PlayAmbientSound(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& filename, bool looping)
14971 {
14972 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14973 sndManager->PlayAsAmbient(filename, looping);
14974 }
14975
14976 bool MusicPlaying(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
14977 {
14978 return true;
14979 }
14980
14981 void SetMasterGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
14982 {
14983 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14984 sndManager->SetMasterGain(gain);
14985 }
14986
14987 void SetMusicGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
14988 {
14989 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14990 sndManager->SetMusicGain(gain);
14991 }
14992
14993 void SetAmbientGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
14994 {
14995 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
14996 sndManager->SetAmbientGain(gain);
14997 }
14998
14999 void SetActionGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
15000 {
15001 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
15002 sndManager->SetActionGain(gain);
15003 }
15004
15005 void SetUIGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain)
15006 {
15007 if (CSoundManager* sndManager = (CSoundManager*)g_SoundManager)
15008 sndManager->SetUIGain(gain);
15009 }
15010
15011 #else
15012
15013 bool MusicPlaying(ScriptInterface::CxPrivate* UNUSED(pCxPrivate) ){ return false; }
15014 void PlayAmbientSound(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& UNUSED(filename), bool UNUSED(looping) ){}
15015 void PlayUISound(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& UNUSED(filename), bool UNUSED(looping) ) {}
15016 void PlayMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& UNUSED(filename), bool UNUSED(looping) ) {}
15017 void StartPlaylist(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool UNUSED(looping) ){}
15018 void AddPlaylistItem(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& UNUSED(filename) ){}
15019 void ClearPlaylist(ScriptInterface::CxPrivate* UNUSED(pCxPrivate) ){}
15020 void StopMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate) ){}
15021 void StartMusic(ScriptInterface::CxPrivate* UNUSED(pCxPrivate) ){}
15022- void SetMasterGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
15023- void SetMusicGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
15024- void SetAmbientGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
15025- void SetActionGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
15026- void SetUIGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float gain){}
15027+ void SetMasterGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15028+ void SetMusicGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15029+ void SetAmbientGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15030+ void SetActionGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15031+ void SetUIGain(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float UNUSED(gain)){}
15032
15033 #endif
15034
15035 void RegisterScriptFunctions(const ScriptInterface& scriptInterface)
15036 {
15037 scriptInterface.RegisterFunction<void, &StartMusic>("StartMusic");
15038 scriptInterface.RegisterFunction<void, &StopMusic>("StopMusic");
15039 scriptInterface.RegisterFunction<void, &ClearPlaylist>("ClearPlaylist");
15040 scriptInterface.RegisterFunction<void, std::wstring, &AddPlaylistItem>("AddPlaylistItem");
15041 scriptInterface.RegisterFunction<void, bool, &StartPlaylist>("StartPlaylist");
15042 scriptInterface.RegisterFunction<void, std::wstring, bool, &PlayMusic>("PlayMusic");
15043 scriptInterface.RegisterFunction<void, std::wstring, bool, &PlayUISound>("PlayUISound");
15044 scriptInterface.RegisterFunction<void, std::wstring, bool, &PlayAmbientSound>("PlayAmbientSound");
15045 scriptInterface.RegisterFunction<bool, &MusicPlaying>("MusicPlaying");
15046 scriptInterface.RegisterFunction<void, float, &SetMasterGain>("SetMasterGain");
15047 scriptInterface.RegisterFunction<void, float, &SetMusicGain>("SetMusicGain");
15048 scriptInterface.RegisterFunction<void, float, &SetAmbientGain>("SetAmbientGain");
15049 scriptInterface.RegisterFunction<void, float, &SetActionGain>("SetActionGain");
15050 scriptInterface.RegisterFunction<void, float, &SetUIGain>("SetUIGain");
15051 }
15052 }
15053Index: source/third_party/tinygettext/src/iconv.cpp
15054===================================================================
15055--- source/third_party/tinygettext/src/iconv.cpp (revision 23275)
15056+++ source/third_party/tinygettext/src/iconv.cpp (working copy)
15057@@ -1,152 +1,152 @@
15058 // tinygettext - A gettext replacement that works directly on .po files
15059 // Copyright (c) 2009 Ingo Ruhnke <grumbel@gmail.com>
15060 //
15061 // This software is provided 'as-is', without any express or implied
15062 // warranty. In no event will the authors be held liable for any damages
15063 // arising from the use of this software.
15064 //
15065 // Permission is granted to anyone to use this software for any purpose,
15066 // including commercial applications, and to alter it and redistribute it
15067 // freely, subject to the following restrictions:
15068 //
15069 // 1. The origin of this software must not be misrepresented; you must not
15070 // claim that you wrote the original software. If you use this software
15071 // in a product, an acknowledgement in the product documentation would be
15072 // appreciated but is not required.
15073 // 2. Altered source versions must be plainly marked as such, and must not be
15074 // misrepresented as being the original software.
15075 // 3. This notice may not be removed or altered from any source distribution.
15076
15077 #include "precompiled.h"
15078
15079 #include <ctype.h>
15080 #include <assert.h>
15081 #include <sstream>
15082 #include <errno.h>
15083 #include <stdexcept>
15084 #include <string.h>
15085 #include <stdlib.h>
15086
15087 #include "tinygettext/iconv.hpp"
15088 #include "tinygettext/log_stream.hpp"
15089
15090 namespace tinygettext {
15091
15092 #ifndef tinygettext_ICONV_CONST
15093 # define tinygettext_ICONV_CONST
15094 #endif
15095
15096 IConv::IConv()
15097 : to_charset(),
15098 from_charset(),
15099 cd(0)
15100 {}
15101
15102 IConv::IConv(const std::string& from_charset_, const std::string& to_charset_)
15103 : to_charset(),
15104 from_charset(),
15105 cd(0)
15106 {
15107 set_charsets(from_charset_, to_charset_);
15108 }
15109
15110 IConv::~IConv()
15111 {
15112 if (cd)
15113 tinygettext_iconv_close(cd);
15114 }
15115
15116 void
15117 IConv::set_charsets(const std::string& from_charset_, const std::string& to_charset_)
15118 {
15119 if (cd)
15120 tinygettext_iconv_close(cd);
15121
15122 from_charset = from_charset_;
15123 to_charset = to_charset_;
15124
15125 for(std::string::iterator i = to_charset.begin(); i != to_charset.end(); ++i)
15126 *i = static_cast<char>(toupper(*i));
15127
15128 for(std::string::iterator i = from_charset.begin(); i != from_charset.end(); ++i)
15129 *i = static_cast<char>(toupper(*i));
15130
15131 if (to_charset == from_charset)
15132 {
15133 cd = 0;
15134 }
15135 else
15136 {
15137 cd = tinygettext_iconv_open(to_charset.c_str(), from_charset.c_str());
15138 if (cd == reinterpret_cast<tinygettext_iconv_t>(-1))
15139 {
15140 if(errno == EINVAL)
15141 {
15142 std::ostringstream str;
15143 str << "IConv construction failed: conversion from '" << from_charset
15144 << "' to '" << to_charset << "' not available";
15145 throw std::runtime_error(str.str());
15146 }
15147 else
15148 {
15149 std::ostringstream str;
15150 str << "IConv: construction failed: " << strerror(errno);
15151 throw std::runtime_error(str.str());
15152 }
15153 }
15154 }
15155 }
15156
15157 /// Convert a string from encoding to another.
15158 std::string
15159 IConv::convert(const std::string& text)
15160 {
15161 if (!cd)
15162 {
15163 return text;
15164 }
15165 else
15166 {
15167 size_t inbytesleft = text.size();
15168 size_t outbytesleft = 4*inbytesleft; // Worst case scenario: ASCII -> UTF-32?
15169
15170 // We try to avoid to much copying around, so we write directly into
15171 // a std::string
15172- tinygettext_ICONV_CONST char* inbuf = const_cast<char*>(&text[0]);
15173+ const char* inbuf = const_cast<char*>(&text[0]);
15174 std::string result(outbytesleft, 'X');
15175 char* outbuf = &result[0];
15176
15177 // Try to convert the text.
15178 size_t ret = tinygettext_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
15179 if (ret == static_cast<size_t>(-1))
15180 {
15181 if (errno == EILSEQ || errno == EINVAL)
15182 { // invalid multibyte sequence
15183 tinygettext_iconv(cd, NULL, NULL, NULL, NULL); // reset state
15184
15185 // FIXME: Could try to skip the invalid byte and continue
15186 log_error << "error: tinygettext:iconv: invalid multibyte sequence in: \"" << text << "\"" << std::endl;
15187 }
15188 else if (errno == E2BIG)
15189 { // output buffer to small
15190 assert(false && "tinygettext/iconv.cpp: E2BIG: This should never be reached");
15191 }
15192 else if (errno == EBADF)
15193 {
15194 assert(false && "tinygettext/iconv.cpp: EBADF: This should never be reached");
15195 }
15196 else
15197 {
15198 assert(false && "tinygettext/iconv.cpp: <unknown>: This should never be reached");
15199 }
15200 }
15201
15202 result.resize(4*text.size() - outbytesleft);
15203
15204 return result;
15205 }
15206 }
15207
15208 } // namespace tinygettext
15209
15210 /* EOF */