1 /* 2 * Copyright 2010-2011, Hamish Morrison, hamish@lavabit.com 3 * Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de 4 * All rights reserved. Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "SettingsWindow.h" 9 #include "Settings.h" 10 11 #include <Application.h> 12 #include <Alert.h> 13 #include <Box.h> 14 #include <Button.h> 15 #include <Catalog.h> 16 #include <CheckBox.h> 17 #include <GroupLayout.h> 18 #include <GroupLayoutBuilder.h> 19 #include <Locale.h> 20 #include <StringView.h> 21 #include <String.h> 22 #include <Slider.h> 23 #include <PopUpMenu.h> 24 #include <MenuItem.h> 25 #include <MenuField.h> 26 #include <Screen.h> 27 #include <FindDirectory.h> 28 #include <Path.h> 29 #include <Volume.h> 30 #include <VolumeRoster.h> 31 32 #include <stdio.h> 33 34 #undef B_TRANSLATE_CONTEXT 35 #define B_TRANSLATE_CONTEXT "SettingsWindow" 36 37 38 static const uint32 kMsgDefaults = 'dflt'; 39 static const uint32 kMsgRevert = 'rvrt'; 40 static const uint32 kMsgSliderUpdate = 'slup'; 41 static const uint32 kMsgSwapEnabledUpdate = 'swen'; 42 static const uint32 kMsgVolumeSelected = 'vlsl'; 43 static const int64 kMegaByte = 1024 * 1024; 44 45 46 class SizeSlider : public BSlider { 47 public: 48 SizeSlider(const char* name, const char* label, 49 BMessage* message, int32 min, int32 max, uint32 flags); 50 virtual ~SizeSlider(); 51 52 virtual const char* UpdateText() const; 53 54 private: 55 mutable BString fText; 56 }; 57 58 59 SizeSlider::SizeSlider(const char* name, const char* label, 60 BMessage* message, int32 min, int32 max, uint32 flags) 61 : BSlider(name, label, message, min, max, B_HORIZONTAL, B_BLOCK_THUMB, flags) 62 { 63 rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR); 64 UseFillColor(true, &color); 65 } 66 67 68 SizeSlider::~SizeSlider() 69 { 70 } 71 72 73 const char* 74 byte_string(int64 size) 75 { 76 double value = 1. * size; 77 static char string[256]; 78 79 if (value < 1024) 80 snprintf(string, sizeof(string), B_TRANSLATE("%Ld B"), size); 81 else { 82 static const char *units[] = { 83 B_TRANSLATE_MARK("KB"), 84 B_TRANSLATE_MARK("MB"), 85 B_TRANSLATE_MARK("GB"), 86 NULL 87 }; 88 int32 i = -1; 89 90 do { 91 value /= 1024.0; 92 i++; 93 } while (value >= 1024 && units[i + 1]); 94 95 off_t rounded = off_t(value * 100LL); 96 snprintf(string, sizeof(string), "%g %s", rounded / 100.0, 97 B_TRANSLATE_NOCOLLECT(units[i])); 98 } 99 100 return string; 101 } 102 103 104 const char* 105 SizeSlider::UpdateText() const 106 { 107 fText = byte_string(Value() * kMegaByte); 108 return fText.String(); 109 } 110 111 112 class VolumeMenuItem : public BMenuItem { 113 public: 114 VolumeMenuItem(const char* label, BMessage* message, BVolume* volume); 115 BVolume* Volume() { return fVolume; } 116 117 private: 118 BVolume* fVolume; 119 }; 120 121 122 VolumeMenuItem::VolumeMenuItem(const char* label, BMessage* message, 123 BVolume* volume) 124 : BMenuItem(label, message) 125 { 126 fVolume = volume; 127 } 128 129 130 SettingsWindow::SettingsWindow() 131 : 132 BWindow(BRect(0, 0, 269, 172), B_TRANSLATE_SYSTEM_NAME("VirtualMemory"), 133 B_TITLED_WINDOW, B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS 134 | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS), 135 fLocked(false) 136 137 { 138 BView* view = new BGroupView(); 139 140 fSwapEnabledCheckBox = new BCheckBox("enable swap", 141 B_TRANSLATE("Enable virtual memory"), 142 new BMessage(kMsgSwapEnabledUpdate)); 143 144 BBox* box = new BBox("box", B_FOLLOW_LEFT_RIGHT); 145 box->SetLabel(fSwapEnabledCheckBox); 146 147 system_info info; 148 get_system_info(&info); 149 150 BString string = B_TRANSLATE("Physical memory: "); 151 string << byte_string((off_t)info.max_pages * B_PAGE_SIZE); 152 BStringView* memoryView = new BStringView("physical memory", string.String()); 153 154 string = B_TRANSLATE("Current swap file size: "); 155 string << byte_string(fSettings.SwapSize()); 156 BStringView* swapfileView = new BStringView("current swap size", string.String()); 157 158 BPopUpMenu* menu = new BPopUpMenu("invalid"); 159 160 // collect volumes 161 // TODO: listen to volume changes! 162 // TODO: accept dropped volumes 163 164 BVolumeRoster volumeRoster; 165 BVolume* volume = new BVolume(); 166 char name[B_FILE_NAME_LENGTH]; 167 while (volumeRoster.GetNextVolume(volume) == B_OK) { 168 if (!volume->IsPersistent() || volume->GetName(name) != B_OK || !name[0]) 169 continue; 170 VolumeMenuItem* item = new VolumeMenuItem(name, 171 new BMessage(kMsgVolumeSelected), volume); 172 menu->AddItem(item); 173 volume = new BVolume(); 174 } 175 176 fVolumeMenuField = new BMenuField("volumes", B_TRANSLATE("Use volume:"), menu); 177 178 fSizeSlider = new SizeSlider("size slider", 179 B_TRANSLATE("Requested swap file size:"), new BMessage(kMsgSliderUpdate), 180 1, 1, B_WILL_DRAW | B_FRAME_EVENTS); 181 fSizeSlider->SetViewColor(255, 0, 255); 182 183 fWarningStringView = new BStringView("", ""); 184 fWarningStringView->SetAlignment(B_ALIGN_CENTER); 185 186 view->SetLayout(new BGroupLayout(B_HORIZONTAL)); 187 view->AddChild(BGroupLayoutBuilder(B_VERTICAL, 10) 188 .AddGroup(B_HORIZONTAL) 189 .Add(memoryView) 190 .AddGlue() 191 .End() 192 .AddGroup(B_HORIZONTAL) 193 .Add(swapfileView) 194 .AddGlue() 195 .End() 196 #ifdef SWAP_VOLUME_IMPLEMENTED 197 .AddGroup(B_HORIZONTAL) 198 .Add(fVolumeMenuField) 199 .AddGlue() 200 .End() 201 #else 202 .AddGlue() 203 #endif 204 .Add(fSizeSlider) 205 .Add(fWarningStringView) 206 .SetInsets(10, 10, 10, 10) 207 ); 208 box->AddChild(view); 209 210 fDefaultsButton = new BButton("defaults", B_TRANSLATE("Defaults"), 211 new BMessage(kMsgDefaults)); 212 213 fRevertButton = new BButton("revert", B_TRANSLATE("Revert"), 214 new BMessage(kMsgRevert)); 215 fRevertButton->SetEnabled(false); 216 217 SetLayout(new BGroupLayout(B_HORIZONTAL)); 218 AddChild(BGroupLayoutBuilder(B_VERTICAL, 10) 219 .Add(box) 220 .AddGroup(B_HORIZONTAL, 10) 221 .Add(fDefaultsButton) 222 .Add(fRevertButton) 223 .AddGlue() 224 .End() 225 .SetInsets(10, 10, 10, 10) 226 ); 227 228 BScreen screen; 229 BRect screenFrame = screen.Frame(); 230 if (!screenFrame.Contains(fSettings.WindowPosition())) 231 CenterOnScreen(); 232 else 233 MoveTo(fSettings.WindowPosition()); 234 235 #ifdef SWAP_VOLUME_IMPLEMENTED 236 // Validate the volume specified in settings file 237 status_t result = fSettings.SwapVolume().InitCheck(); 238 239 if (result != B_OK) { 240 int32 choice = (new BAlert("VirtualMemory", B_TRANSLATE( 241 "The swap volume specified in the settings file is invalid.\n" 242 "You can keep the current setting or switch to the " 243 "default swap volume."), 244 B_TRANSLATE("Keep"), B_TRANSLATE("Switch"), NULL, 245 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 246 if (choice == 1) { 247 BVolumeRoster volumeRoster; 248 BVolume bootVolume; 249 volumeRoster.GetBootVolume(&bootVolume); 250 fSettings.SetSwapVolume(bootVolume); 251 } 252 } 253 #endif 254 255 _Update(); 256 } 257 258 259 SettingsWindow::~SettingsWindow() 260 { 261 } 262 263 264 void 265 SettingsWindow::MessageReceived(BMessage* message) 266 { 267 switch (message->what) { 268 case kMsgRevert: 269 fSettings.RevertSwapChanges(); 270 _Update(); 271 break; 272 case kMsgDefaults: 273 _SetSwapDefaults(); 274 _Update(); 275 break; 276 case kMsgSliderUpdate: 277 fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte); 278 _Update(); 279 break; 280 case kMsgVolumeSelected: 281 fSettings.SetSwapVolume(*((VolumeMenuItem*)fVolumeMenuField->Menu() 282 ->FindMarked())->Volume()); 283 _Update(); 284 break; 285 case kMsgSwapEnabledUpdate: 286 { 287 int32 value; 288 if (message->FindInt32("be:value", &value) != B_OK) 289 break; 290 291 if (value == 0) { 292 // print out warning, give the user the time to think about it :) 293 // ToDo: maybe we want to remove this possibility in the GUI 294 // as Be did, but I thought a proper warning could be helpful 295 // (for those that want to change that anyway) 296 int32 choice = (new BAlert("VirtualMemory", 297 B_TRANSLATE( 298 "Disabling virtual memory will have unwanted effects on " 299 "system stability once the memory is used up.\n" 300 "Virtual memory does not affect system performance " 301 "until this point is reached.\n\n" 302 "Are you really sure you want to turn it off?"), 303 B_TRANSLATE("Turn off"), B_TRANSLATE("Keep enabled"), NULL, 304 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 305 if (choice == 1) { 306 fSwapEnabledCheckBox->SetValue(1); 307 break; 308 } 309 } 310 311 fSettings.SetSwapEnabled(value != 0); 312 _Update(); 313 break; 314 } 315 316 default: 317 BWindow::MessageReceived(message); 318 } 319 } 320 321 322 bool 323 SettingsWindow::QuitRequested() 324 { 325 fSettings.SetWindowPosition(Frame().LeftTop()); 326 be_app->PostMessage(B_QUIT_REQUESTED); 327 return true; 328 } 329 330 331 void 332 SettingsWindow::_Update() 333 { 334 if ((fSwapEnabledCheckBox->Value() != 0) != fSettings.SwapEnabled()) 335 fSwapEnabledCheckBox->SetValue(fSettings.SwapEnabled()); 336 337 #ifdef SWAP_VOLUME_IMPLEMENTED 338 if (fVolumeMenuField->IsEnabled() != fSettings.SwapEnabled()) 339 fVolumeMenuField->SetEnabled(fSettings.SwapEnabled()); 340 VolumeMenuItem* selectedVolumeItem = 341 (VolumeMenuItem*)fVolumeMenuField->Menu()->FindMarked(); 342 if (selectedVolumeItem == NULL) { 343 VolumeMenuItem* currentVolumeItem; 344 int32 items = fVolumeMenuField->Menu()->CountItems(); 345 for (int32 index = 0; index < items; ++index) { 346 currentVolumeItem = ((VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(index)); 347 if (*(currentVolumeItem->fVolume) == fSettings.SwapVolume()) { 348 currentVolumeItem->SetMarked(true); 349 break; 350 } 351 } 352 } else if (*selectedVolumeItem->fVolume != fSettings.SwapVolume()) { 353 VolumeMenuItem* currentVolumeItem; 354 int32 items = fVolumeMenuField->Menu()->CountItems(); 355 for (int32 index = 0; index < items; ++index) { 356 currentVolumeItem = ((VolumeMenuItem*)fVolumeMenuField->Menu()->ItemAt(index)); 357 if (*(currentVolumeItem->fVolume) == fSettings.SwapVolume()) { 358 currentVolumeItem->SetMarked(true); 359 break; 360 } 361 } 362 } 363 #endif 364 365 fWarningStringView->SetText(""); 366 fLocked = false; 367 368 if (fSettings.IsRevertible()) 369 fWarningStringView->SetText( 370 B_TRANSLATE("Changes will take effect on restart!")); 371 if (fRevertButton->IsEnabled() != fSettings.IsRevertible()) 372 fRevertButton->SetEnabled(fSettings.IsRevertible()); 373 374 off_t minSize, maxSize; 375 if (_GetSwapFileLimits(minSize, maxSize) == B_OK) { 376 // round to nearest MB -- slider steps in whole MBs 377 (minSize >>= 20) <<= 20; 378 (maxSize >>= 20) <<= 20; 379 BString minLabel, maxLabel; 380 minLabel << byte_string(minSize); 381 maxLabel << byte_string(maxSize); 382 if (minLabel != fSizeSlider->MinLimitLabel() 383 || maxLabel != fSizeSlider->MaxLimitLabel()) { 384 fSizeSlider->SetLimitLabels(minLabel.String(), maxLabel.String()); 385 fSizeSlider->SetLimits(minSize / kMegaByte, maxSize / kMegaByte); 386 } 387 } else if (fSettings.SwapEnabled()) { 388 fWarningStringView->SetText( 389 B_TRANSLATE("Insufficient space for a swap file.")); 390 fLocked = true; 391 } 392 if (fSizeSlider->Value() != fSettings.SwapSize() / kMegaByte) 393 fSizeSlider->SetValue(fSettings.SwapSize() / kMegaByte); 394 if (fSizeSlider->IsEnabled() != fSettings.SwapEnabled() || fLocked) 395 { 396 fSizeSlider->SetEnabled(fSettings.SwapEnabled() && !fLocked); 397 fSettings.SetSwapSize((off_t)fSizeSlider->Value() * kMegaByte); 398 } 399 } 400 401 402 status_t 403 SettingsWindow::_GetSwapFileLimits(off_t& minSize, off_t& maxSize) 404 { 405 minSize = kMegaByte; 406 407 // maximum size is the free space on the current volume 408 // (minus some safety offset, depending on the disk size) 409 off_t freeSpace = fSettings.SwapVolume().FreeBytes(); 410 off_t safetyFreeSpace = fSettings.SwapVolume().Capacity() / 100; 411 if (safetyFreeSpace > 1024 * kMegaByte) 412 safetyFreeSpace = 1024 * kMegaByte; 413 414 // check if there already is a page file on this disk and 415 // adjust the free space accordingly 416 BPath path; 417 if (find_directory(B_COMMON_VAR_DIRECTORY, &path, false, 418 &fSettings.SwapVolume()) == B_OK) { 419 path.Append("swap"); 420 BEntry swap(path.Path()); 421 422 off_t size; 423 if (swap.GetSize(&size) == B_OK) { 424 // If swap file exists, forget about safety space; 425 // disk may have filled after creation of swap file. 426 safetyFreeSpace = 0; 427 freeSpace += size; 428 } 429 } 430 431 maxSize = freeSpace - safetyFreeSpace; 432 if (maxSize < minSize) { 433 maxSize = 0; 434 minSize = 0; 435 return B_ERROR; 436 } 437 438 return B_OK; 439 } 440 441 442 void 443 SettingsWindow::_SetSwapDefaults() 444 { 445 fSettings.SetSwapEnabled(true); 446 447 BVolumeRoster volumeRoster; 448 BVolume temporaryVolume; 449 volumeRoster.GetBootVolume(&temporaryVolume); 450 fSettings.SetSwapVolume(temporaryVolume); 451 452 system_info info; 453 get_system_info(&info); 454 455 off_t defaultSize = (off_t)info.max_pages * B_PAGE_SIZE; 456 off_t minSize, maxSize; 457 _GetSwapFileLimits(minSize, maxSize); 458 459 if (defaultSize > maxSize / 2) 460 defaultSize = maxSize / 2; 461 462 fSettings.SetSwapSize(defaultSize); 463 } 464 465