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	fSwapEnabledCheckBox(NULL),
125	fSwapAutomaticCheckBox(NULL),
126	fSizeSlider(NULL),
127	fDefaultsButton(NULL),
128	fRevertButton(NULL),
129	fWarningStringView(NULL),
130	fVolumeMenuField(NULL),
131	fSwapUsageBar(NULL),
132	fSetupComplete(false)
133{
134	gBootDev = dev_for_path("/boot");
135	BAlignment align(B_ALIGN_LEFT, B_ALIGN_MIDDLE);
136
137	if (fSettings.ReadWindowSettings() != B_OK)
138		CenterOnScreen();
139	else
140		MoveTo(fSettings.WindowPosition());
141
142	status_t result = fSettings.ReadSwapSettings();
143	if (result == kErrorSettingsNotFound)
144		fSettings.DefaultSwapSettings(false);
145	else if (result == kErrorSettingsInvalid) {
146		int32 choice = (new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
147			B_TRANSLATE("The settings specified in the settings file "
148			"are invalid. You can load the defaults or quit."),
149			B_TRANSLATE("Load defaults"), B_TRANSLATE("Quit")))->Go();
150		if (choice == 1) {
151			be_app->PostMessage(B_QUIT_REQUESTED);
152			return;
153		}
154		fSettings.DefaultSwapSettings(false);
155	} else if (result == kErrorVolumeNotFound) {
156		int32 choice = (new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
157			B_TRANSLATE("The volume specified in the settings file "
158			"could not be found. You can use the boot volume or quit."),
159			B_TRANSLATE("Use boot volume"), B_TRANSLATE("Quit")))->Go();
160		if (choice == 1) {
161			be_app->PostMessage(B_QUIT_REQUESTED);
162			return;
163		}
164		fSettings.SetSwapVolume(gBootDev, false);
165	}
166
167	fSwapEnabledCheckBox = new BCheckBox("enable swap",
168		B_TRANSLATE("Enable virtual memory"),
169		new BMessage(kMsgSwapEnabledUpdate));
170	fSwapEnabledCheckBox->SetExplicitAlignment(align);
171
172	fSwapAutomaticCheckBox = new BCheckBox("auto swap",
173		B_TRANSLATE("Automatic swap management"),
174		new BMessage(kMsgSwapAutomaticUpdate));
175	fSwapEnabledCheckBox->SetExplicitAlignment(align);
176
177	fSwapUsageBar = new BStatusBar("swap usage");
178
179	BPopUpMenu* menu = new BPopUpMenu("volume menu");
180	fVolumeMenuField = new BMenuField("volume menu field",
181		B_TRANSLATE("Use volume:"), menu);
182	fVolumeMenuField->SetExplicitAlignment(align);
183
184	BVolumeRoster roster;
185	BVolume vol;
186	while (roster.GetNextVolume(&vol) == B_OK) {
187		if (!vol.IsPersistent() || vol.IsReadOnly() || vol.IsRemovable()
188			|| vol.IsShared())
189			continue;
190		_AddVolumeMenuItem(vol.Device());
191	}
192
193	watch_node(NULL, B_WATCH_MOUNT, this, this);
194
195	fSizeSlider = new SizeSlider("size slider",
196		B_TRANSLATE("Requested swap file size:"),
197		new BMessage(kMsgSliderUpdate),	0, 0, B_WILL_DRAW | B_FRAME_EVENTS);
198	fSizeSlider->SetViewColor(255, 0, 255);
199	fSizeSlider->SetExplicitAlignment(align);
200
201	fWarningStringView = new BStringView("warning",
202		B_TRANSLATE("Changes will take effect upon reboot."));
203
204	BBox* box = new BBox("box");
205	box->SetLabel(fSwapEnabledCheckBox);
206
207	box->AddChild(BLayoutBuilder::Group<>(B_VERTICAL)
208		.SetInsets(B_USE_DEFAULT_SPACING)
209		.Add(fSwapUsageBar)
210		.Add(fSwapAutomaticCheckBox)
211		.Add(fVolumeMenuField)
212		.Add(fSizeSlider)
213		.Add(fWarningStringView)
214		.View());
215
216	fDefaultsButton = new BButton("defaults", B_TRANSLATE("Defaults"),
217		new BMessage(kMsgDefaults));
218
219	fRevertButton = new BButton("revert", B_TRANSLATE("Revert"),
220		new BMessage(kMsgRevert));
221	fRevertButton->SetEnabled(false);
222
223	BLayoutBuilder::Group<>(this, B_VERTICAL)
224		.SetInsets(B_USE_WINDOW_SPACING)
225		.Add(box)
226		.AddGroup(B_HORIZONTAL)
227			.Add(fDefaultsButton)
228			.Add(fRevertButton)
229			.AddGlue()
230		.End();
231
232	BScreen screen;
233	BRect screenFrame = screen.Frame();
234	if (!screenFrame.Contains(fSettings.WindowPosition()))
235		CenterOnScreen();
236
237#ifdef SWAP_VOLUME_IMPLEMENTED
238	// Validate the volume specified in settings file
239	status_t result = fSettings.SwapVolume().InitCheck();
240
241	if (result != B_OK) {
242		BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("VirtualMemory"),
243			B_TRANSLATE("The swap volume specified in the settings file is ",
244			"invalid.\n You can keep the current setting or switch to the "
245			"default swap volume."),
246			B_TRANSLATE("Keep"), B_TRANSLATE("Switch"), NULL,
247			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
248		alert->SetShortcut(0, B_ESCAPE);
249		int32 choice = alert->Go();
250		if (choice == 1) {
251			BVolumeRoster volumeRoster;
252			BVolume bootVolume;
253			volumeRoster.GetBootVolume(&bootVolume);
254			fSettings.SetSwapVolume(bootVolume);
255		}
256	}
257#endif
258
259	_Update();
260
261	// TODO: We may want to run this at an interval
262	_UpdateSwapInfo();
263	fSetupComplete = true;
264}
265
266
267void
268SettingsWindow::MessageReceived(BMessage* message)
269{
270	switch (message->what) {
271		case B_NODE_MONITOR:
272		{
273			int32 opcode;
274			if (message->FindInt32("opcode", &opcode) != B_OK)
275				break;
276			dev_t device;
277			if (opcode == B_DEVICE_MOUNTED
278				&& message->FindInt32("new device", &device) == B_OK) {
279				BVolume vol(device);
280				if (!vol.IsPersistent() || vol.IsReadOnly()
281					|| vol.IsRemovable() || vol.IsShared()) {
282					break;
283				}
284				_AddVolumeMenuItem(device);
285			} else if (opcode == B_DEVICE_UNMOUNTED
286				&& message->FindInt32("device", &device) == B_OK) {
287				_RemoveVolumeMenuItem(device);
288			}
289			_Update();
290			break;
291		}
292		case kMsgRevert:
293			fSettings.RevertSwapSettings();
294			_Update();
295			break;
296		case kMsgDefaults:
297			fSettings.DefaultSwapSettings();
298			_Update();
299			break;
300		case kMsgSliderUpdate:
301			_RecordChoices();
302			_Update();
303			break;
304		case kMsgVolumeSelected:
305			_RecordChoices();
306			_Update();
307			break;
308		case kMsgSwapEnabledUpdate:
309		{
310			if (fSwapEnabledCheckBox->Value() == 0) {
311				// print out warning, give the user the
312				// time to think about it :)
313				// ToDo: maybe we want to remove this possibility in the GUI
314				// as Be did, but I thought a proper warning could be helpful
315				// (for those that want to change that anyway)
316				BAlert* alert = new BAlert(
317					B_TRANSLATE_SYSTEM_NAME("VirtualMemory"), B_TRANSLATE(
318					"Disabling virtual memory will have unwanted effects on "
319					"system stability once the memory is used up.\n"
320					"Virtual memory does not affect system performance "
321					"until this point is reached.\n\n"
322					"Are you really sure you want to turn it off?"),
323					B_TRANSLATE("Turn off"), B_TRANSLATE("Keep enabled"), NULL,
324					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
325				alert->SetShortcut(1, B_ESCAPE);
326				int32 choice = alert->Go();
327				if (choice == 1) {
328					fSwapEnabledCheckBox->SetValue(1);
329					break;
330				}
331			}
332
333			_RecordChoices();
334			_Update();
335			break;
336		}
337		case kMsgSwapAutomaticUpdate:
338		{
339			_RecordChoices();
340			_Update();
341			break;
342		}
343
344		default:
345			BWindow::MessageReceived(message);
346	}
347}
348
349
350bool
351SettingsWindow::QuitRequested()
352{
353	if (!fSetupComplete)
354		return true;
355
356	fSettings.SetWindowPosition(Frame().LeftTop());
357	_RecordChoices();
358	fSettings.WriteWindowSettings();
359	fSettings.WriteSwapSettings();
360	be_app->PostMessage(B_QUIT_REQUESTED);
361	return true;
362}
363
364
365status_t
366SettingsWindow::_AddVolumeMenuItem(dev_t device)
367{
368	if (_FindVolumeMenuItem(device) != NULL)
369		return B_ERROR;
370
371	VolumeMenuItem* item = new VolumeMenuItem(device,
372		new BMessage(kMsgVolumeSelected));
373
374	fs_info info;
375	if (fs_stat_dev(device, &info) == 0) {
376		node_ref node;
377		node.device = info.dev;
378		node.node = info.root;
379		AddHandler(item);
380		watch_node(&node, B_WATCH_NAME, item);
381	}
382
383	fVolumeMenuField->Menu()->AddItem(item);
384	return B_OK;
385}
386
387
388status_t
389SettingsWindow::_RemoveVolumeMenuItem(dev_t device)
390{
391	VolumeMenuItem* item = _FindVolumeMenuItem(device);
392	if (item != NULL) {
393		fVolumeMenuField->Menu()->RemoveItem(item);
394		delete item;
395		return B_OK;
396	}
397	return B_ERROR;
398}
399
400
401VolumeMenuItem*
402SettingsWindow::_FindVolumeMenuItem(dev_t device)
403{
404	VolumeMenuItem* item = NULL;
405	int32 count = fVolumeMenuField->Menu()->CountItems();
406	for (int i = 0; i < count; i++) {
407		item = (VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(i);
408		if (item->Volume().Device() == device)
409			return item;
410	}
411
412	return NULL;
413}
414
415
416void
417SettingsWindow::_RecordChoices()
418{
419	fSettings.SetSwapAutomatic(fSwapAutomaticCheckBox->Value());
420	fSettings.SetSwapEnabled(fSwapEnabledCheckBox->Value());
421	fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte);
422	fSettings.SetSwapVolume(((VolumeMenuItem*)fVolumeMenuField
423		->Menu()->FindMarked())->Volume().Device());
424}
425
426
427void
428SettingsWindow::_Update()
429{
430	fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled());
431	fSwapAutomaticCheckBox->SetValue(fSettings.SwapAutomatic());
432
433	VolumeMenuItem* item = _FindVolumeMenuItem(fSettings.SwapVolume());
434	if (item != NULL) {
435		fSizeSlider->SetEnabled(true);
436		item->SetMarked(true);
437		BEntry swapFile;
438		if (gBootDev == item->Volume().Device())
439			swapFile.SetTo("/var/swap");
440		else {
441			BDirectory root;
442			item->Volume().GetRootDirectory(&root);
443			swapFile.SetTo(&root, "swap");
444		}
445
446		off_t swapFileSize = 0;
447		swapFile.GetSize(&swapFileSize);
448
449		char sizeStr[16];
450
451		off_t freeSpace = item->Volume().FreeBytes() + swapFileSize;
452		off_t safeSpace = freeSpace - (off_t)(0.15 * freeSpace);
453		(safeSpace >>= 20) <<= 20;
454		off_t minSize = B_PAGE_SIZE + kMegaByte;
455		(minSize >>= 20) <<= 20;
456		BString minLabel, maxLabel;
457		minLabel << string_for_size(minSize, sizeStr, sizeof(sizeStr));
458		maxLabel << string_for_size(safeSpace, sizeStr, sizeof(sizeStr));
459
460		fSizeSlider->SetLimitLabels(minLabel.String(), maxLabel.String());
461		fSizeSlider->SetLimits(minSize / kMegaByte, safeSpace / kMegaByte);
462		fSizeSlider->SetValue(fSettings.SwapSize() / kMegaByte);
463	} else
464		fSizeSlider->SetEnabled(false);
465
466	bool revertable = fSettings.IsRevertable();
467	if (revertable)
468		fWarningStringView->Show();
469	else
470		fWarningStringView->Hide();
471
472	fRevertButton->SetEnabled(revertable);
473	fDefaultsButton->SetEnabled(fSettings.IsDefaultable());
474
475	// Automatic Swap depends on swap being enabled
476	fSwapAutomaticCheckBox->SetEnabled(fSettings.SwapEnabled());
477
478	// Manual swap settings depend on enabled swap
479	// and automatic swap being disabled
480	fSizeSlider->SetEnabled(fSettings.SwapEnabled()
481		&& !fSwapAutomaticCheckBox->Value());
482	fVolumeMenuField->SetEnabled(fSettings.SwapEnabled()
483		&& !fSwapAutomaticCheckBox->Value());
484}
485
486
487void
488SettingsWindow::_UpdateSwapInfo()
489{
490	system_info info;
491	get_system_info(&info);
492
493	off_t currentSwapSize = info.max_swap_pages * B_PAGE_SIZE;
494	off_t currentSwapUsed
495		= (info.max_swap_pages - info.free_swap_pages) * B_PAGE_SIZE;
496
497	char sizeStr[16];
498	BString swapSizeStr = string_for_size(currentSwapSize, sizeStr,
499		sizeof(sizeStr));
500	BString swapUsedStr = string_for_size(currentSwapUsed, sizeStr,
501		sizeof(sizeStr));
502
503	BString string = swapUsedStr << " / " << swapSizeStr;
504
505	fSwapUsageBar->SetMaxValue(currentSwapSize / kMegaByte);
506	fSwapUsageBar->Update(currentSwapUsed / kMegaByte,
507		B_TRANSLATE("Current Swap:"), string.String());
508}
509