1/*
2 * Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de
3 * All rights reserved. Distributed under the terms of the MIT License.
4 *
5 * Copyright 2010-2012 Haiku, Inc. All rights reserved.
6 * Distributed under the terms of the MIT License.
7 *
8 * Authors:
9 *		Hamish Morrison, hamish@lavabit.com
10 *		Alexander von Gluck, kallisti5@unixzen.com
11 */
12
13
14#include "SettingsWindow.h"
15
16#include <Application.h>
17#include <Alert.h>
18#include <Box.h>
19#include <Button.h>
20#include <Catalog.h>
21#include <CheckBox.h>
22#include <Directory.h>
23#include <FindDirectory.h>
24#include <LayoutBuilder.h>
25#include <MenuItem.h>
26#include <MenuField.h>
27#include <NodeMonitor.h>
28#include <Path.h>
29#include <PopUpMenu.h>
30#include <Screen.h>
31#include <StringForSize.h>
32#include <StringView.h>
33#include <String.h>
34#include <Slider.h>
35#include <system_info.h>
36#include <Volume.h>
37#include <VolumeRoster.h>
38
39#include "Settings.h"
40
41
42#undef B_TRANSLATION_CONTEXT
43#define B_TRANSLATION_CONTEXT "SettingsWindow"
44
45
46static const uint32 kMsgDefaults = 'dflt';
47static const uint32 kMsgRevert = 'rvrt';
48static const uint32 kMsgSliderUpdate = 'slup';
49static const uint32 kMsgSwapEnabledUpdate = 'swen';
50static const uint32 kMsgSwapAutomaticUpdate = 'swat';
51static const uint32 kMsgVolumeSelected = 'vlsl';
52static const off_t kMegaByte = 1024 * 1024;
53static dev_t gBootDev = -1;
54
55
56SizeSlider::SizeSlider(const char* name, const char* label,
57	BMessage* message, int32 min, int32 max, uint32 flags)
58	:
59	BSlider(name, label, message, min, max, B_HORIZONTAL,
60		B_BLOCK_THUMB, flags)
61{
62	rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
63	UseFillColor(true, &color);
64}
65
66
67const char*
68SizeSlider::UpdateText() const
69{
70	return string_for_size(Value() * kMegaByte, fText, sizeof(fText));
71}
72
73
74VolumeMenuItem::VolumeMenuItem(BVolume volume, BMessage* message)
75	:
76	BMenuItem("", message),
77	fVolume(volume)
78{
79	GenerateLabel();
80}
81
82
83void
84VolumeMenuItem::MessageReceived(BMessage* message)
85{
86	if (message->what == B_NODE_MONITOR) {
87		int32 code;
88		if (message->FindInt32("opcode", &code) == B_OK)
89			if (code == B_ENTRY_MOVED)
90				GenerateLabel();
91	}
92}
93
94
95void
96VolumeMenuItem::GenerateLabel()
97{
98	char name[B_FILE_NAME_LENGTH + 1];
99	fVolume.GetName(name);
100
101	BDirectory dir;
102	if (fVolume.GetRootDirectory(&dir) == B_OK) {
103		BEntry entry;
104		if (dir.GetEntry(&entry) == B_OK) {
105			BPath path;
106			if (entry.GetPath(&path) == B_OK) {
107				BString label;
108				label << name << " (" << path.Path() << ")";
109				SetLabel(label);
110				return;
111			}
112		}
113	}
114
115	SetLabel(name);
116}
117
118
119SettingsWindow::SettingsWindow()
120	:
121	BWindow(BRect(0, 0, 269, 172), B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
122		B_TITLED_WINDOW, B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS
123		| B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS)
124
125{
126	gBootDev = dev_for_path("/boot");
127	BAlignment align(B_ALIGN_LEFT, B_ALIGN_MIDDLE);
128
129	if (fSettings.ReadWindowSettings() != B_OK)
130		CenterOnScreen();
131	else
132		MoveTo(fSettings.WindowPosition());
133
134	status_t result = fSettings.ReadSwapSettings();
135	if (result == kErrorSettingsNotFound)
136		fSettings.DefaultSwapSettings(false);
137	else if (result == kErrorSettingsInvalid) {
138		int32 choice = (new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
139			B_TRANSLATE("The settings specified in the settings file "
140			"are invalid. You can load the defaults or quit."),
141			B_TRANSLATE("Load defaults"), B_TRANSLATE("Quit")))->Go();
142		if (choice == 1) {
143			be_app->PostMessage(B_QUIT_REQUESTED);
144			return;
145		}
146		fSettings.DefaultSwapSettings(false);
147	} else if (result == kErrorVolumeNotFound) {
148		int32 choice = (new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
149			B_TRANSLATE("The volume specified in the settings file "
150			"could not be found. You can use the boot volume or quit."),
151			B_TRANSLATE("Use boot volume"), B_TRANSLATE("Quit")))->Go();
152		if (choice == 1) {
153			be_app->PostMessage(B_QUIT_REQUESTED);
154			return;
155		}
156		fSettings.SetSwapVolume(gBootDev, false);
157	}
158
159	fSwapEnabledCheckBox = new BCheckBox("enable swap",
160		B_TRANSLATE("Enable virtual memory"),
161		new BMessage(kMsgSwapEnabledUpdate));
162	fSwapEnabledCheckBox->SetExplicitAlignment(align);
163
164	fSwapAutomaticCheckBox = new BCheckBox("auto swap",
165		B_TRANSLATE("Automatic swap management"),
166		new BMessage(kMsgSwapAutomaticUpdate));
167	fSwapEnabledCheckBox->SetExplicitAlignment(align);
168
169	fSwapUsageBar = new BStatusBar("swap usage");
170
171	BPopUpMenu* menu = new BPopUpMenu("volume menu");
172	fVolumeMenuField = new BMenuField("volume menu field",
173		B_TRANSLATE("Use volume:"), menu);
174	fVolumeMenuField->SetExplicitAlignment(align);
175
176	BVolumeRoster roster;
177	BVolume vol;
178	while (roster.GetNextVolume(&vol) == B_OK) {
179		if (!vol.IsPersistent() || vol.IsReadOnly() || vol.IsRemovable()
180			|| vol.IsShared())
181			continue;
182		_AddVolumeMenuItem(vol.Device());
183	}
184
185	watch_node(NULL, B_WATCH_MOUNT, this, this);
186
187	fSizeSlider = new SizeSlider("size slider",
188		B_TRANSLATE("Requested swap file size:"),
189		new BMessage(kMsgSliderUpdate),	0, 0, B_WILL_DRAW | B_FRAME_EVENTS);
190	fSizeSlider->SetViewColor(255, 0, 255);
191	fSizeSlider->SetExplicitAlignment(align);
192
193	fWarningStringView = new BStringView("warning",
194		B_TRANSLATE("Changes will take effect upon reboot."));
195
196	BBox* box = new BBox("box");
197	box->SetLabel(fSwapEnabledCheckBox);
198
199	box->AddChild(BLayoutBuilder::Group<>(B_VERTICAL, B_USE_DEFAULT_SPACING)
200		.Add(fSwapUsageBar)
201		.Add(fSwapAutomaticCheckBox)
202		.Add(fVolumeMenuField)
203		.Add(fSizeSlider)
204		.Add(fWarningStringView)
205		.SetInsets(10)
206		.View());
207
208	fDefaultsButton = new BButton("defaults", B_TRANSLATE("Defaults"),
209		new BMessage(kMsgDefaults));
210
211	fRevertButton = new BButton("revert", B_TRANSLATE("Revert"),
212		new BMessage(kMsgRevert));
213	fRevertButton->SetEnabled(false);
214
215	BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING)
216		.Add(box)
217		.AddGroup(B_HORIZONTAL, 10)
218			.Add(fDefaultsButton)
219			.Add(fRevertButton)
220			.AddGlue()
221		.End()
222		.SetInsets(10);
223
224	BScreen screen;
225	BRect screenFrame = screen.Frame();
226	if (!screenFrame.Contains(fSettings.WindowPosition()))
227		CenterOnScreen();
228
229#ifdef SWAP_VOLUME_IMPLEMENTED
230	// Validate the volume specified in settings file
231	status_t result = fSettings.SwapVolume().InitCheck();
232
233	if (result != B_OK) {
234		BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
235			B_TRANSLATE("The swap volume specified in the settings file is ",
236			"invalid.\n You can keep the current setting or switch to the "
237			"default swap volume."),
238			B_TRANSLATE("Keep"), B_TRANSLATE("Switch"), NULL,
239			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
240		alert->SetShortcut(0, B_ESCAPE);
241		int32 choice = alert->Go();
242		if (choice == 1) {
243			BVolumeRoster volumeRoster;
244			BVolume bootVolume;
245			volumeRoster.GetBootVolume(&bootVolume);
246			fSettings.SetSwapVolume(bootVolume);
247		}
248	}
249#endif
250
251	_Update();
252
253	// TODO: We may want to run this at an interval
254	_UpdateSwapInfo();
255}
256
257
258void
259SettingsWindow::MessageReceived(BMessage* message)
260{
261	switch (message->what) {
262		case B_NODE_MONITOR:
263		{
264			int32 opcode;
265			if (message->FindInt32("opcode", &opcode) != B_OK)
266				break;
267			dev_t device;
268			if (opcode == B_DEVICE_MOUNTED
269				&& message->FindInt32("new device", &device) == B_OK) {
270				BVolume vol(device);
271				if (!vol.IsPersistent() || vol.IsReadOnly()
272					|| vol.IsRemovable() || vol.IsShared()) {
273					break;
274				}
275				_AddVolumeMenuItem(device);
276			} else if (opcode == B_DEVICE_UNMOUNTED
277				&& message->FindInt32("device", &device) == B_OK) {
278				_RemoveVolumeMenuItem(device);
279			}
280			_Update();
281			break;
282		}
283		case kMsgRevert:
284			fSettings.RevertSwapSettings();
285			_Update();
286			break;
287		case kMsgDefaults:
288			fSettings.DefaultSwapSettings();
289			_Update();
290			break;
291		case kMsgSliderUpdate:
292			_RecordChoices();
293			_Update();
294			break;
295		case kMsgVolumeSelected:
296			_RecordChoices();
297			_Update();
298			break;
299		case kMsgSwapEnabledUpdate:
300		{
301			if (fSwapEnabledCheckBox->Value() == 0) {
302				// print out warning, give the user the
303				// time to think about it :)
304				// ToDo: maybe we want to remove this possibility in the GUI
305				// as Be did, but I thought a proper warning could be helpful
306				// (for those that want to change that anyway)
307				BAlert* alert = new BAlert(
308					B_TRANSLATE_SYSTEM_NAME("VirtualMemory"), B_TRANSLATE(
309					"Disabling virtual memory will have unwanted effects on "
310					"system stability once the memory is used up.\n"
311					"Virtual memory does not affect system performance "
312					"until this point is reached.\n\n"
313					"Are you really sure you want to turn it off?"),
314					B_TRANSLATE("Turn off"), B_TRANSLATE("Keep enabled"), NULL,
315					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
316				alert->SetShortcut(1, B_ESCAPE);
317				int32 choice = alert->Go();
318				if (choice == 1) {
319					fSwapEnabledCheckBox->SetValue(1);
320					break;
321				}
322			}
323
324			_RecordChoices();
325			_Update();
326			break;
327		}
328		case kMsgSwapAutomaticUpdate:
329		{
330			_RecordChoices();
331			_Update();
332			break;
333		}
334
335		default:
336			BWindow::MessageReceived(message);
337	}
338}
339
340
341bool
342SettingsWindow::QuitRequested()
343{
344	fSettings.SetWindowPosition(Frame().LeftTop());
345
346	_RecordChoices();
347	fSettings.WriteWindowSettings();
348	fSettings.WriteSwapSettings();
349	be_app->PostMessage(B_QUIT_REQUESTED);
350	return true;
351}
352
353
354status_t
355SettingsWindow::_AddVolumeMenuItem(dev_t device)
356{
357	if (_FindVolumeMenuItem(device) != NULL)
358		return B_ERROR;
359
360	VolumeMenuItem* item = new VolumeMenuItem(device,
361		new BMessage(kMsgVolumeSelected));
362
363	fs_info info;
364	if (fs_stat_dev(device, &info) == 0) {
365		node_ref node;
366		node.device = info.dev;
367		node.node = info.root;
368		AddHandler(item);
369		watch_node(&node, B_WATCH_NAME, item);
370	}
371
372	fVolumeMenuField->Menu()->AddItem(item);
373	return B_OK;
374}
375
376
377status_t
378SettingsWindow::_RemoveVolumeMenuItem(dev_t device)
379{
380	VolumeMenuItem* item = _FindVolumeMenuItem(device);
381	if (item != NULL) {
382		fVolumeMenuField->Menu()->RemoveItem(item);
383		delete item;
384		return B_OK;
385	}
386	return B_ERROR;
387}
388
389
390VolumeMenuItem*
391SettingsWindow::_FindVolumeMenuItem(dev_t device)
392{
393	VolumeMenuItem* item = NULL;
394	int32 count = fVolumeMenuField->Menu()->CountItems();
395	for (int i = 0; i < count; i++) {
396		item = (VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(i);
397		if (item->Volume().Device() == device)
398			return item;
399	}
400
401	return NULL;
402}
403
404
405void
406SettingsWindow::_RecordChoices()
407{
408	fSettings.SetSwapAutomatic(fSwapAutomaticCheckBox->Value());
409	fSettings.SetSwapEnabled(fSwapEnabledCheckBox->Value());
410	fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte);
411	fSettings.SetSwapVolume(((VolumeMenuItem*)fVolumeMenuField
412		->Menu()->FindMarked())->Volume().Device());
413}
414
415
416void
417SettingsWindow::_Update()
418{
419	fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled());
420	fSwapAutomaticCheckBox->SetValue(fSettings.SwapAutomatic());
421
422	VolumeMenuItem* item = _FindVolumeMenuItem(fSettings.SwapVolume());
423	if (item != NULL) {
424		fSizeSlider->SetEnabled(true);
425		item->SetMarked(true);
426		BEntry swapFile;
427		if (gBootDev == item->Volume().Device())
428			swapFile.SetTo("/var/swap");
429		else {
430			BDirectory root;
431			item->Volume().GetRootDirectory(&root);
432			swapFile.SetTo(&root, "swap");
433		}
434
435		off_t swapFileSize = 0;
436		swapFile.GetSize(&swapFileSize);
437
438		char sizeStr[16];
439
440		off_t freeSpace = item->Volume().FreeBytes() + swapFileSize;
441		off_t safeSpace = freeSpace - (off_t)(0.15 * freeSpace);
442		(safeSpace >>= 20) <<= 20;
443		off_t minSize = B_PAGE_SIZE + kMegaByte;
444		(minSize >>= 20) <<= 20;
445		BString minLabel, maxLabel;
446		minLabel << string_for_size(minSize, sizeStr, sizeof(sizeStr));
447		maxLabel << string_for_size(safeSpace, sizeStr, sizeof(sizeStr));
448
449		fSizeSlider->SetLimitLabels(minLabel.String(), maxLabel.String());
450		fSizeSlider->SetLimits(minSize / kMegaByte, safeSpace / kMegaByte);
451		fSizeSlider->SetValue(fSettings.SwapSize() / kMegaByte);
452	} else
453		fSizeSlider->SetEnabled(false);
454
455	bool revertable = fSettings.IsRevertable();
456	if (revertable)
457		fWarningStringView->Show();
458	else
459		fWarningStringView->Hide();
460
461	fRevertButton->SetEnabled(revertable);
462	fDefaultsButton->SetEnabled(fSettings.IsDefaultable());
463
464	// Automatic Swap depends on swap being enabled
465	fSwapAutomaticCheckBox->SetEnabled(fSettings.SwapEnabled());
466
467	// Manual swap settings depend on enabled swap
468	// and automatic swap being disabled
469	fSizeSlider->SetEnabled(fSettings.SwapEnabled()
470		&& !fSwapAutomaticCheckBox->Value());
471	fVolumeMenuField->SetEnabled(fSettings.SwapEnabled()
472		&& !fSwapAutomaticCheckBox->Value());
473}
474
475
476void
477SettingsWindow::_UpdateSwapInfo()
478{
479	system_memory_info memInfo = {};
480	__get_system_info_etc(B_MEMORY_INFO, &memInfo, sizeof(memInfo));
481
482	off_t currentSwapSize = memInfo.max_swap_space;
483	off_t currentSwapUsed = (memInfo.max_swap_space - memInfo.free_swap_space);
484
485	char sizeStr[16];
486	BString swapSizeStr = string_for_size(currentSwapSize, sizeStr,
487		sizeof(sizeStr));
488	BString swapUsedStr = string_for_size(currentSwapUsed, sizeStr,
489		sizeof(sizeStr));
490
491	BString string = swapUsedStr << " / " << swapSizeStr;
492
493	fSwapUsageBar->SetMaxValue(currentSwapSize / kMegaByte);
494	fSwapUsageBar->Update(currentSwapUsed / kMegaByte,
495		B_TRANSLATE("Current Swap:"), string.String());
496}
497