xref: /haiku/src/preferences/virtualmemory/SettingsWindow.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
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 
46 static const uint32 kMsgDefaults = 'dflt';
47 static const uint32 kMsgRevert = 'rvrt';
48 static const uint32 kMsgSliderUpdate = 'slup';
49 static const uint32 kMsgSwapEnabledUpdate = 'swen';
50 static const uint32 kMsgSwapAutomaticUpdate = 'swat';
51 static const uint32 kMsgVolumeSelected = 'vlsl';
52 static const off_t kMegaByte = 1024 * 1024;
53 static dev_t gBootDev = -1;
54 
55 
56 SizeSlider::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 
67 const char*
68 SizeSlider::UpdateText() const
69 {
70 	return string_for_size(Value() * kMegaByte, fText, sizeof(fText));
71 }
72 
73 
74 VolumeMenuItem::VolumeMenuItem(BVolume volume, BMessage* message)
75 	:
76 	BMenuItem("", message),
77 	fVolume(volume)
78 {
79 	GenerateLabel();
80 }
81 
82 
83 void
84 VolumeMenuItem::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 
95 void
96 VolumeMenuItem::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 
119 SettingsWindow::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 
267 void
268 SettingsWindow::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 
350 bool
351 SettingsWindow::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 
365 status_t
366 SettingsWindow::_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 
388 status_t
389 SettingsWindow::_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 
401 VolumeMenuItem*
402 SettingsWindow::_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 
416 void
417 SettingsWindow::_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 
427 void
428 SettingsWindow::_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 
487 void
488 SettingsWindow::_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