1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 // Not actually part of forms, but is handy. 5 6 module dfl.environment; 7 8 import core.sys.windows.windows; 9 10 import dfl.internal.dlib; 11 import dfl.exception; 12 import dfl.internal.clib; 13 14 import dfl.base; 15 import dfl.internal.utf; 16 import dfl.event; 17 18 final class Environment { 19 private this() { 20 } 21 22 static: 23 24 @property Dstring commandLine() { 25 return dfl.internal.utf.getCommandLine(); 26 } 27 28 @property void currentDirectory(Dstring cd) { 29 if (!dfl.internal.utf.setCurrentDirectory(cd)) { 30 throw new DflException("Unable to set current directory"); 31 } 32 } 33 34 @property Dstring currentDirectory() { 35 return dfl.internal.utf.getCurrentDirectory(); 36 } 37 38 @property Dstring machineName() { 39 Dstring result; 40 result = dfl.internal.utf.getComputerName(); 41 if (!result.length) { 42 throw new DflException("Unable to obtain machine name"); 43 } 44 return result; 45 } 46 47 @property Dstring newLine() { 48 return nativeLineSeparatorString; 49 } 50 51 @property OperatingSystem osVersion() { 52 OSVERSIONINFOA osi; 53 Version ver; 54 55 osi.dwOSVersionInfoSize = osi.sizeof; 56 if (!GetVersionExA(&osi)) { 57 throw new DflException("Unable to obtain operating system version information"); 58 } 59 60 int build; 61 62 switch (osi.dwPlatformId) { 63 case VER_PLATFORM_WIN32_NT: 64 ver = new Version(osi.dwMajorVersion, osi.dwMinorVersion, osi.dwBuildNumber); 65 break; 66 67 case VER_PLATFORM_WIN32_WINDOWS: 68 ver = new Version(osi.dwMajorVersion, osi.dwMinorVersion, LOWORD(osi.dwBuildNumber)); 69 break; 70 71 default: 72 ver = new Version(osi.dwMajorVersion, osi.dwMinorVersion); 73 } 74 75 return new OperatingSystem(cast(PlatformId) osi.dwPlatformId, ver); 76 } 77 78 @property Dstring systemDirectory() { 79 Dstring result; 80 result = dfl.internal.utf.getSystemDirectory(); 81 if (!result.length) { 82 throw new DflException("Unable to obtain system directory"); 83 } 84 return result; 85 } 86 87 // Should return int ? 88 @property DWORD tickCount() { 89 return GetTickCount(); 90 } 91 92 @property Dstring userName() { 93 Dstring result; 94 result = dfl.internal.utf.getUserName(); 95 if (!result.length) { 96 throw new DflException("Unable to obtain user name"); 97 } 98 return result; 99 } 100 101 void exit(int code) { 102 // This is probably better than ExitProcess(code). 103 exit(code); 104 } 105 106 Dstring expandEnvironmentVariables(Dstring str) { 107 if (!str.length) { 108 return str; 109 } 110 Dstring result; 111 if (!dfl.internal.utf.expandEnvironmentStrings(str, result)) { 112 throw new DflException("Unable to expand environment variables"); 113 } 114 return result; 115 } 116 117 Dstring[] getCommandLineArgs() { 118 return parseArgs(commandLine); 119 } 120 121 Dstring getEnvironmentVariable(Dstring name, bool throwIfMissing) { 122 Dstring result; 123 result = dfl.internal.utf.getEnvironmentVariable(name); 124 if (!result.length) { 125 if (!throwIfMissing) { 126 if (GetLastError() == 203) { // ERROR_ENVVAR_NOT_FOUND 127 return null; 128 } 129 } 130 throw new DflException("Unable to obtain environment variable"); 131 } 132 return result; 133 } 134 135 Dstring getEnvironmentVariable(Dstring name) { 136 return getEnvironmentVariable(name, true); 137 } 138 139 //Dstring[Dstring] getEnvironmentVariables() 140 //Dstring[] getEnvironmentVariables() 141 142 Dstring[] getLogicalDrives() { 143 DWORD dr = GetLogicalDrives(); 144 Dstring[] result; 145 int i; 146 char[4] tmp = " :\\\0"; 147 148 for (i = 0; dr; i++) { 149 if (dr & 1) { 150 char[] s = tmp.dup[0 .. 3]; 151 s[0] = cast(char)('A' + i); 152 //result ~= s; 153 result ~= cast(Dstring) s; // Needed in D2. 154 } 155 dr >>= 1; 156 } 157 158 return result; 159 } 160 } 161 162 /+ 163 enum PowerModes: ubyte { 164 STATUS_CHANGE, 165 RESUME, 166 SUSPEND, 167 } 168 169 170 class PowerModeChangedEventArgs: EventArgs { 171 this(PowerModes pm) { 172 this._pm = pm; 173 } 174 175 176 @property final PowerModes mode() { 177 return _pm; 178 } 179 180 181 private: 182 PowerModes _pm; 183 } 184 +/ 185 186 /+ 187 188 enum SessionEndReasons: ubyte { 189 SYSTEM_SHUTDOWN, /// 190 LOGOFF, 191 } 192 193 194 195 class SystemEndedEventArgs: EventArgs { 196 197 this(SessionEndReasons reason) { 198 this._reason = reason; 199 } 200 201 202 203 final @property SessionEndReasons reason() { 204 return this._reason; 205 } 206 207 208 private: 209 SessionEndReasons _reason; 210 } 211 212 213 214 class SessionEndingEventArgs: EventArgs { 215 216 this(SessionEndReasons reason) { 217 this._reason = reason; 218 } 219 220 221 222 final @property SessionEndReasons reason() { 223 return this._reason; 224 } 225 226 227 228 final @property void cancel(bool byes) { 229 this._cancel = byes; 230 } 231 232 233 final @property bool cancel() { 234 return this._cancel; 235 } 236 237 238 private: 239 SessionEndReasons _reason; 240 bool _cancel = false; 241 } 242 +/ 243 244 /+ 245 final class SystemEvents { 246 private this() {} 247 248 249 static: 250 EventHandler displaySettingsChanged; 251 EventHandler installedFontsChanged; 252 EventHandler lowMemory; // GC automatically collects before this event. 253 EventHandler paletteChanged; 254 //PowerModeChangedEventHandler powerModeChanged; // WM_POWERBROADCAST 255 SystemEndedEventHandler systemEnded; 256 SessionEndingEventHandler systemEnding; 257 SessionEndingEventHandler sessionEnding; 258 EventHandler timeChanged; 259 // user preference changing/changed. WM_SETTINGCHANGE ? 260 261 262 /+ 263 @property void useOwnThread(bool byes) { 264 if(byes != useOwnThread) { 265 if(byes) { 266 _ownthread = new Thread; 267 // idle priority.. 268 } else { 269 // Kill thread. 270 } 271 } 272 } 273 274 275 @property bool useOwnThread() { 276 return _ownthread !is null; 277 } 278 +/ 279 280 281 private: 282 //package Thread _ownthread = null; 283 284 285 SessionEndReasons sessionEndReasonFromLparam(LPARAM lparam) { 286 if(ENDSESSION_LOGOFF == lparam) { 287 return SessionEndReasons.LOGOFF; 288 } 289 return SessionEndReasons.SYSTEM_SHUTDOWN; 290 } 291 292 293 void _realCheckMessage(ref Message m) { 294 switch(m.msg) { 295 case WM_DISPLAYCHANGE: 296 displaySettingsChanged(typeid(SystemEvents), EventArgs.empty); 297 break; 298 299 case WM_FONTCHANGE: 300 installedFontsChanged(typeid(SystemEvents), EventArgs.empty); 301 break; 302 303 case WM_COMPACTING: 304 //gcFullCollect(); 305 lowMemory(typeid(SystemEvents), EventArgs.empty); 306 break; 307 308 case WM_PALETTECHANGED: 309 paletteChanged(typeid(SystemEvents), EventArgs.empty); 310 break; 311 312 case WM_ENDSESSION: 313 if(m.wParam) { 314 scope SystemEndedEventArgs ea = new SystemEndedEventArgs(sessionEndReasonFromLparam(m.lParam)); 315 systemEnded(typeid(SystemEvents), ea); 316 } 317 break; 318 319 case WM_QUERYENDSESSION: { 320 scope SessionEndingEventArgs ea = new SessionEndingEventArgs(sessionEndReasonFromLparam(m.lParam)); 321 systemEnding(typeid(SystemEvents), ea); 322 if(ea.cancel) { 323 m.result = FALSE; // Stop shutdown. 324 } 325 m.result = TRUE; // Continue shutdown. 326 } 327 break; 328 329 case WM_TIMECHANGE: 330 timeChanged(typeid(SystemEvents), EventArgs.empty); 331 break; 332 333 default: 334 } 335 } 336 337 338 package void _checkMessage(ref Message m) { 339 //if(_ownthread) 340 _realCheckMessage(m); 341 } 342 } 343 +/ 344 345 package Dstring[] parseArgs(Dstring args) { 346 Dstring[] result; 347 uint i; 348 bool inQuote = false; 349 bool findStart = true; 350 uint startIndex = 0; 351 352 for (i = 0;; i++) { 353 if (i == args.length) { 354 if (findStart) { 355 startIndex = i; 356 } 357 break; 358 } 359 360 if (findStart) { 361 if (args[i] == ' ' || args[i] == '\t') { 362 continue; 363 } 364 findStart = false; 365 startIndex = i; 366 } 367 368 if (args[i] == '"') { 369 inQuote = !inQuote; 370 if (!inQuote) { //matched quotes 371 result.length = result.length + 1; 372 result[result.length - 1] = args[startIndex .. i]; 373 findStart = true; 374 } else { //starting quote 375 if (startIndex != i) { //must be a quote stuck to another word, separate them 376 result.length = result.length + 1; 377 result[result.length - 1] = args[startIndex .. i]; 378 startIndex = i + 1; 379 } else { 380 startIndex++; //exclude the quote 381 } 382 } 383 } else if (!inQuote) { 384 if (args[i] == ' ' || args[i] == '\t') { 385 result.length = result.length + 1; 386 result[result.length - 1] = args[startIndex .. i]; 387 findStart = true; 388 } 389 } 390 } 391 392 if (startIndex != i) { 393 result.length = result.length + 1; 394 result[result.length - 1] = args[startIndex .. i]; 395 } 396 397 return result; 398 } 399 400 unittest { 401 Dstring[] args; 402 403 args = parseArgs(`"foo" bar`); 404 assert(args.length == 2); 405 assert(args[0] == "foo"); 406 assert(args[1] == "bar"); 407 408 args = parseArgs(`"environment"`); 409 assert(args.length == 1); 410 assert(args[0] == "environment"); 411 412 /+ 413 writefln("commandLine = '%s'", Environment.commandLine); 414 foreach(Dstring arg; Environment.getCommandLineArgs()) { 415 writefln("\t'%s'", arg); 416 } 417 +/ 418 } 419 420 // Any version, not just the operating system. 421 class Version { // docmain ? 422 private: 423 int _major = 0, _minor = 0; 424 int _build = -1, _revision = -1; 425 426 public: 427 428 this() { 429 } 430 431 final: 432 433 // A string containing "major.minor.build.revision". 434 // 2 to 4 parts expected. 435 this(Dstring str) { 436 Dstring[] stuff = stringSplit(str, "."); 437 438 switch (stuff.length) { 439 case 4: 440 _revision = stringToInt(stuff[3]); 441 goto case 3; 442 case 3: 443 _build = stringToInt(stuff[2]); 444 goto case 2; 445 case 2: 446 _minor = stringToInt(stuff[1]); 447 _major = stringToInt(stuff[0]); 448 break; 449 default: 450 throw new DflException("Invalid version parameter"); 451 } 452 } 453 454 this(int major, int minor) { 455 _major = major; 456 _minor = minor; 457 } 458 459 this(int major, int minor, int build) { 460 _major = major; 461 _minor = minor; 462 _build = build; 463 } 464 465 this(int major, int minor, int build, int revision) { 466 _major = major; 467 _minor = minor; 468 _build = build; 469 _revision = revision; 470 } 471 472 /+ // D2 doesn't like this without () but this invariant doesn't really even matter. 473 invariant { 474 assert(_major >= 0); 475 assert(_minor >= 0); 476 assert(_build >= -1); 477 assert(_revision >= -1); 478 } 479 +/ 480 481 override Dstring toString() { 482 Dstring result; 483 484 result = intToString(_major) ~ "." ~ intToString(_minor); 485 if (_build != -1) { 486 result ~= "." ~ intToString(_build); 487 } 488 if (_revision != -1) { 489 result ~= "." ~ intToString(_revision); 490 } 491 492 return result; 493 } 494 495 @property int major() { 496 return _major; 497 } 498 499 @property int minor() { 500 return _minor; 501 } 502 503 // -1 if no build. 504 @property int build() { 505 return _build; 506 } 507 508 // -1 if no revision. 509 @property int revision() { 510 return _revision; 511 } 512 } 513 514 enum PlatformId : DWORD { 515 WIN_CE = cast(DWORD)-1, 516 WIN32s = VER_PLATFORM_WIN32s, 517 WIN32_WINDOWS = VER_PLATFORM_WIN32_WINDOWS, 518 WIN32_NT = VER_PLATFORM_WIN32_NT, 519 } 520 521 final class OperatingSystem { 522 final { 523 524 this(PlatformId platId, Version ver) { 525 this.platId = platId; 526 this.vers = ver; 527 } 528 529 override Dstring toString() { 530 Dstring result; 531 532 // DMD 0.92 says error: cannot implicitly convert uint to PlatformId 533 switch (cast(DWORD) platId) { 534 case PlatformId.WIN32_NT: 535 result = "Microsoft Windows NT "; 536 break; 537 538 case PlatformId.WIN32_WINDOWS: 539 result = "Microsoft Windows 95 "; 540 break; 541 542 case PlatformId.WIN32s: 543 result = "Microsoft Win32s "; 544 break; 545 546 case PlatformId.WIN_CE: 547 result = "Microsoft Windows CE "; 548 break; 549 550 default: 551 throw new DflException("Unknown platform ID"); 552 } 553 554 result ~= vers.toString(); 555 return result; 556 } 557 558 @property PlatformId platform() { 559 return platId; 560 } 561 562 // Should be version() :p 563 @property Version ver() { 564 return vers; 565 } 566 } 567 568 private: 569 PlatformId platId; 570 Version vers; 571 }