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