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