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