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