1 /* 2 * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license. 3 * 4 * Pixel Perfect Engine, config module 5 */ 6 7 module PixelPerfectEngine.system.config; 8 9 //import std.xml; 10 import std.file; 11 import std.stdio; 12 import std..string; 13 import std.conv; 14 //import std.csv; 15 16 import PixelPerfectEngine.system.input.handler; 17 import PixelPerfectEngine.system.exc; 18 import PixelPerfectEngine.system.etc; 19 import PixelPerfectEngine.system.dictionary; 20 import PixelPerfectEngine.graphics.outputScreen; 21 22 import bindbc.sdl; 23 24 import sdlang; 25 /** 26 * Defines a single keybinding. 27 */ 28 public struct KeyBinding { 29 BindingCode bc; ///The code that will be used for the keybinding. 30 string name; ///The name of the keybinding. 31 float[2] deadzones; ///Defines a deadzone for the axis. 32 bool axisAsButton;///True if axis is emulating a button outside of deadzone. 33 34 ///Converts the struct's other portion into an InputBinding 35 public InputBinding toInputBinding() @nogc @safe pure nothrow const { 36 import collections.commons : defaultHash; 37 return InputBinding(defaultHash(name), axisAsButton ? InputBinding.IS_AXIS_AS_BUTTON : 0, deadzones); 38 } 39 } 40 41 /** 42 * Stores basic InputDevice info alongside with some additional settings 43 */ 44 public struct InputDeviceData{ 45 public int deviceNumber; ///Number of the device that is being used 46 public Devicetype type; ///Type of the device (keyboard, joystick, etc) 47 public bool enableForceFeedback; ///Toggles force feedback if device is capable of it 48 public string name; ///Name of the device 49 public KeyBinding[] keyBindingList; ///List of the Keybindings associated with this device 50 public this(int deviceNumber, Devicetype type, string name){ 51 this.deviceNumber = deviceNumber; 52 this.type = type; 53 this.name = name; 54 } 55 } 56 /** 57 * Handles configuration files, like key configurations, 58 */ 59 public class ConfigurationProfile { 60 public static const ubyte[string] keymodifierStrings; 61 public static const string[ubyte] joymodifierStrings; 62 public static const string[Devicetype] devicetypeStrings; 63 private static Dictionary keyNameDict, joyButtonNameDict, joyAxisNameDict; 64 public int sfxVol, musicVol; 65 public int threads; 66 public string screenMode, resolution, scalingQuality, driver; 67 //public string[string] videoSettings; 68 //public KeyBinding[] keyBindingList; 69 public InputDeviceData[] inputDevices; ///Stores all input devices and keybindings 70 private string path; 71 ///Stores ancillary tags to be serialized into the config file 72 protected Tag[] ancillaryTags; 73 private static string vaultPath; 74 private SDL_DisplayMode[] videoModes; 75 //public AuxillaryElements auxillaryElements[]; 76 public string appName; ///Name of the application. Can be used to check e.g. version safety. 77 public string appVers; ///Version of the application. Can be used to check e.g. version safety. 78 /// Initializes a basic configuration profile. If [vaultPath] doesn't have any configfiles, restores it from defaults. 79 public this() { 80 path = vaultPath ~ "config.sdl"; 81 if(!exists(path)) 82 std.file.copy("../system/defaultConfig.sdl",path); 83 restore(); 84 } 85 /// Initializes a basic configuration profile with user supplied values. 86 /// If [vaultPath] doesn't have any configfiles, restores it from defaults. 87 public this(string filename, string defaultFile) { 88 path = vaultPath ~ filename; 89 if(!exists(path)) 90 std.file.copy(defaultFile, path); 91 restore(); 92 } 93 static this() { 94 keymodifierStrings = 95 ["none" : KeyModifier.None, "Shift": KeyModifier.Shift, "Ctrl": KeyModifier.Ctrl, "Alt": KeyModifier.Alt, 96 "GUI": KeyModifier.GUI, "NumLock": KeyModifier.NumLock, "CapsLock": KeyModifier.CapsLock, "Mode": KeyModifier.Mode, 97 "ScrollLock": KeyModifier.ScrollLock, "All": KeyModifier.All]; 98 joymodifierStrings = [0x00: "button",0x04: "dpad",0x08: "axis"]; 99 devicetypeStrings = [Devicetype.Joystick: "joystick", Devicetype.Keyboard: "keyboard", Devicetype.Mouse: "mouse", 100 Devicetype.Touchscreen: "touchscreen" ]; 101 //keyNameDict = new Dictionary("../system/keycodeNamings.sdl"); 102 keyNameDict = new Dictionary(parseFile("../system/scancodes.sdl")); 103 Tag xinput = parseFile("../system/xinputCodes.sdl"); 104 joyButtonNameDict = new Dictionary(xinput.expectTag("button")); 105 joyAxisNameDict = new Dictionary(xinput.expectTag("axis")); 106 } 107 ///Restores configuration profile from a file. 108 public void restore() { 109 Tag root; 110 111 try { 112 root = parseFile(path); 113 foreach(Tag t0; root.tags) { 114 if (t0.name == "configurationFile") { //get configfile metadata 115 appName = t0.values[0].get!string(); 116 appVers = t0.values[1].get!string(); 117 } else if (t0.name == "audio") { //get values for the audio subsystem 118 sfxVol = t0.getTagValue!int("soundVol", 100); 119 musicVol = t0.getTagValue!int("musicVol", 100); 120 } else if (t0.name == "video") { //get values for the video subsystem 121 foreach(Tag t1; t0.tags ){ 122 switch(t1.name){ 123 case "driver": driver = t1.getValue!string("software"); break; 124 case "scaling": scalingQuality = t1.getValue!string("nearest"); break; 125 case "screenMode": driver = t1.getValue!string("windowed"); break; 126 case "resolution": driver = t1.getValue!string("0"); break; 127 case "threads": threads = t1.getValue!int(-1); break; 128 default: break; 129 } 130 } 131 } else if (t0.name == "input") { 132 foreach(Tag t1; t0.tags) { 133 switch(t1.name) { 134 case "device": 135 InputDeviceData device; 136 device.name = t1.getValue!string(""); 137 device.deviceNumber = t1.getAttribute!int("devNum"); 138 switch(t1.expectAttribute!string("type")){ 139 case "keyboard": 140 device.type = Devicetype.Keyboard; 141 foreach(Tag t2; t1.tags){ 142 if(t2.name is null){ 143 KeyBinding kb; 144 kb.name = t2.expectValue!string(); 145 kb.bc.deviceNum = cast(ubyte)device.deviceNumber; 146 kb.bc.deviceTypeID = Devicetype.Keyboard; 147 kb.bc.modifierFlags = stringToKeymod(t2.getAttribute!string("keyMod", "None")); 148 kb.bc.keymodIgnore = stringToKeymod(t2.getAttribute!string("keyModIgnore", "All")); 149 kb.bc.buttonNum = cast(ushort)(t2.getAttribute!int("code", keyNameDict.decode(t2.getAttribute!string("name")))); 150 device.keyBindingList ~= kb; 151 } 152 } 153 break; 154 case "joystick": 155 device.type = Devicetype.Joystick; 156 foreach(Tag t2; t1.tags) { //parse each individual binding 157 if(t2.name is null) { 158 KeyBinding kb; 159 kb.name = t2.expectValue!string(); 160 kb.bc.deviceNum = cast(ubyte)device.deviceNumber; 161 kb.bc.deviceTypeID = Devicetype.Joystick; 162 switch(t2.getAttribute!string("keyMod")){ 163 case "dpad": 164 kb.bc.modifierFlags = JoyModifier.DPad; 165 goto default; 166 case "axis": 167 kb.bc.modifierFlags = JoyModifier.Axis; 168 kb.deadzones[0] = t2.getAttribute!float("deadZone0"); 169 kb.deadzones[1] = t2.getAttribute!float("deadZone1"); 170 kb.axisAsButton = t2.getAttribute!bool("axisAsButton"); 171 goto default; 172 default: 173 kb.bc.buttonNum = cast(ushort)t2.getAttribute!int("code", joyButtonNameDict.decode(t2.getAttribute!string("name"))); 174 break; 175 } 176 device.keyBindingList ~= kb; 177 } else if(t2.name == "enableForceFeedback") { 178 device.enableForceFeedback = t2.getValue!bool(true); 179 } 180 } 181 break; 182 case "mouse": 183 device.type = Devicetype.Mouse; 184 foreach(Tag t2; t1.tags){ 185 if(t2.name is null){ 186 //const ushort scanCode = cast(ushort)t2.getAttribute!int("code"); 187 KeyBinding kb; 188 kb.name = t2.expectValue!string(); 189 kb.bc.deviceTypeID = Devicetype.Mouse; 190 //keyBindingList ~= KeyBinding(0, scanCode, devicenumber, t2.expectValue!string(), Devicetype.MOUSE); 191 } 192 } 193 break; 194 default: 195 //device = InputDeviceData(devicenumber, Devicetype.KEYBOARD, name); 196 break; 197 } 198 inputDevices ~= device; 199 break; 200 default: break; 201 } 202 } 203 } else { 204 //collect all ancillary tags into an array 205 //t0.remove(); 206 ancillaryTags ~= t0; 207 } 208 } 209 } 210 catch(ParseException e){ 211 writeln(e.msg); 212 } 213 214 215 216 } 217 /** 218 * Stores configuration profile on disk. 219 */ 220 public void store(){ 221 try { 222 Tag root = new Tag(null, null); //, [Value(appName), Value(appVers)] 223 224 new Tag(root, null, "configurationFile", [Value(appName), Value(appVers)]); 225 226 Tag t0 = new Tag(root, null, "audio"); 227 new Tag(t0, null, "soundVol", [Value(sfxVol)]); 228 new Tag(t0, null, "musicVolt", [Value(musicVol)]); 229 230 Tag t1 = new Tag(root, null, "video"); 231 new Tag(t1, null, "driver", [Value(driver)]); 232 new Tag(t1, null, "scaling", [Value(scalingQuality)]); 233 new Tag(t1, null, "screenMode", [Value(screenMode)]); 234 new Tag(t1, null, "resolution", [Value(resolution)]); 235 new Tag(t1, null, "threads", [Value(threads)]); 236 237 Tag t2 = new Tag(root, null, "input"); 238 foreach (InputDeviceData idd; inputDevices) { 239 string devType = devicetypeStrings[idd.type]; 240 Tag t2_0 = new Tag(t2, null, "device", null, [new Attribute(null, "name",Value(idd.name)), new Attribute(null, 241 "type", Value(devType)), new Attribute(null, "devNum", Value(idd.deviceNumber))]); 242 final switch (idd.type) with (Devicetype) { 243 case Keyboard: 244 foreach (binding ; idd.keyBindingList) { 245 Attribute[] attrList = [new Attribute(null, "name", Value(keyNameDict.encode(binding.bc.buttonNum)))]; 246 if (binding.bc.modifierFlags != KeyModifier.None) 247 attrList ~= new Attribute(null, "keyMod", Value(keymodToString(binding.bc.modifierFlags))); 248 if (binding.bc.keymodIgnore != KeyModifier.All) 249 attrList ~= new Attribute(null, "keyModIgnore", Value(keymodToString(binding.bc.keymodIgnore))); 250 new Tag(t2_0, null, null, [Value(binding.name)], attrList); 251 } 252 break; 253 case Joystick: 254 foreach (binding ; idd.keyBindingList) { 255 Attribute[] attrList;//= [new Attribute(null, "name", Value(joyButtonNameDict.encode(binding.bc.buttonNum)))]; 256 switch (binding.bc.modifierFlags) { 257 case JoyModifier.Axis: 258 attrList = [new Attribute(null, "name", Value(joyAxisNameDict.encode(binding.bc.buttonNum))), 259 new Attribute(null, "keyMod", Value(joymodifierStrings[binding.bc.modifierFlags])), 260 new Attribute(null, "deadZone0", Value(binding.deadzones[0])), 261 new Attribute(null, "deadZone1", Value(binding.deadzones[1]))]; 262 if (binding.axisAsButton) 263 attrList ~= new Attribute(null, "axisAsButton", Value(true)); 264 break; 265 case JoyModifier.DPad: 266 attrList = [new Attribute(null, "code", Value(cast(int)(binding.bc.buttonNum))), 267 new Attribute(null, "keyMod", Value(joymodifierStrings[binding.bc.modifierFlags]))]; 268 break; 269 default: 270 attrList = [new Attribute(null, "name", Value(joyButtonNameDict.encode(binding.bc.buttonNum)))]; 271 break; 272 } 273 new Tag(t2_0, null, null, [Value(binding.name)], attrList); 274 } 275 new Tag(t2_0, null, "enableForceFeedback", [Value(idd.enableForceFeedback)]); 276 break; 277 case Mouse: 278 foreach (binding ; idd.keyBindingList) { 279 Attribute[] attrList = [new Attribute(null, "code", Value(cast(int)(binding.bc.buttonNum)))]; 280 new Tag(t2_0, null, null, [Value(binding.name)], attrList); 281 } 282 break; 283 case Touchscreen: 284 break; 285 } 286 } 287 //Tag t3 = new Tag(root, null, "etc"); 288 foreach(at; ancillaryTags){ 289 at.remove(); 290 root.add(at); 291 } 292 string data = root.toSDLDocument(); 293 std.file.write(path, data); 294 } catch (Exception e) { 295 debug writeln(e); 296 } 297 } 298 /** 299 * Converts a key modifier string to machine-readable value 300 */ 301 public ubyte stringToKeymod(string s) @safe const { 302 import std.algorithm.iteration : splitter; 303 if(s == "None") return KeyModifier.None; 304 if(s == "All") return KeyModifier.All; 305 auto values = s.splitter(';'); 306 ubyte result; 307 foreach(t ; values){ 308 result |= keymodifierStrings.get(t,0); 309 } 310 return result; 311 } 312 /** 313 * Converts a key modifier value to human-readable string. 314 */ 315 public string keymodToString(const ubyte keymod) @safe pure nothrow const { 316 if(keymod == KeyModifier.None) 317 return "None"; 318 if(keymod == KeyModifier.All) 319 return "All"; 320 string result; 321 if(keymod & KeyModifier.Shift){ 322 result ~= "Shift;"; 323 } 324 if(keymod & KeyModifier.Ctrl){ 325 result ~= "Ctrl;"; 326 } 327 if(keymod & KeyModifier.Alt){ 328 result ~= "Alt;"; 329 } 330 if(keymod & KeyModifier.GUI){ 331 result ~= "GUI;"; 332 } 333 if(keymod & KeyModifier.NumLock){ 334 result ~= "NumLock;"; 335 } 336 if(keymod & KeyModifier.CapsLock){ 337 result ~= "CapsLock;"; 338 } 339 if(keymod & KeyModifier.Mode){ 340 result ~= "Mode;"; 341 } 342 if(keymod & KeyModifier.ScrollLock){ 343 result ~= "ScrollLock;"; 344 } 345 return result[0..$-1]; 346 } 347 /** 348 * Converts JoyModifier to human-readable string. 349 */ 350 public string joymodToString(const ushort s) @safe pure nothrow const { 351 switch(s) { 352 case JoyModifier.Axis: return "Axis"; 353 case JoyModifier.DPad: return "DPad"; 354 default: return "Buttons"; 355 } 356 } 357 /** 358 * Loads inputbindings into a handler. 359 */ 360 public void loadBindings(InputHandler ih) @safe nothrow { 361 foreach (iD; inputDevices) { 362 foreach (KeyBinding key; iD.keyBindingList) { 363 ih.addBinding(key.bc, key.toInputBinding); 364 } 365 } 366 } 367 public void useVideoMode(int mode, OutputScreen window){ 368 369 } 370 public void autodetectVideoModes(int display = 0){ 371 int displaymodes = SDL_GetNumDisplayModes(display); 372 //writeln(displaymodes); 373 //writeln(to!string(SDL_GetError())); 374 for(int i ; i <= displaymodes ; i++){ 375 SDL_DisplayMode d = SDL_DisplayMode(); 376 if(SDL_GetDisplayMode(display,i,&d) == 0){ 377 378 videoModes ~= d; 379 380 } 381 } 382 } 383 public size_t getNumOfVideoModes(){ 384 return videoModes.length; 385 } 386 public string videoModeToString(size_t n){ 387 return to!string(videoModes[n].w) ~ "x" ~ to!string(videoModes[n].h) ~ "@" ~ to!string(videoModes[n].refresh_rate) ~ "Hz"; 388 } 389 /** 390 * Sets the the path where configuration files and etc. will be stored. 391 * If ../_debug/ folder exists, it'll be used instead for emulation purposes. 392 */ 393 public static void setVaultPath(const char* developer, const char* application){ 394 if (exists("../_debug/")) { 395 vaultPath = "../_debug/"; 396 } else { 397 vaultPath = to!string(SDL_GetPrefPath(developer, application)); 398 } 399 } 400 public static string getVaultPath(){ 401 return vaultPath; 402 } 403 404 } 405 /** 406 * Default keywords to look up for common video settings 407 */ 408 public enum VideoConfigDefaults : string{ 409 SCREENMODE = "screenMode", 410 RESOLUTION = "resolution", 411 SCALINGQUALITY = "scalingQuality", 412 DRIVER = "driver", 413 THREADS = "threads", 414 }