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 }