1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/gtk/task_manager_gtk.h" 6 7 #include <gdk/gdkkeysyms.h> 8 9 #include <algorithm> 10 #include <set> 11 #include <utility> 12 #include <vector> 13 14 #include "base/auto_reset.h" 15 #include "base/command_line.h" 16 #include "base/logging.h" 17 #include "base/prefs/pref_service.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "chrome/browser/browser_process.h" 20 #include "chrome/browser/defaults.h" 21 #include "chrome/browser/memory_purger.h" 22 #include "chrome/browser/prefs/scoped_user_pref_update.h" 23 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h" 24 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 25 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 26 #include "chrome/browser/ui/gtk/gtk_tree.h" 27 #include "chrome/browser/ui/gtk/gtk_util.h" 28 #include "chrome/browser/ui/gtk/menu_gtk.h" 29 #include "chrome/browser/ui/host_desktop.h" 30 #include "chrome/common/chrome_switches.h" 31 #include "chrome/common/pref_names.h" 32 #include "grit/chromium_strings.h" 33 #include "grit/ui_resources.h" 34 #include "third_party/skia/include/core/SkBitmap.h" 35 #include "ui/base/gtk/gtk_hig_constants.h" 36 #include "ui/base/l10n/l10n_util.h" 37 #include "ui/base/models/simple_menu_model.h" 38 #include "ui/base/resource/resource_bundle.h" 39 #include "ui/gfx/gtk_util.h" 40 #include "ui/gfx/image/image.h" 41 42 namespace { 43 44 // The task manager window default size. 45 const int kDefaultWidth = 460; 46 const int kDefaultHeight = 270; 47 48 // The resource id for the 'End process' button. 49 const gint kTaskManagerResponseKill = 1; 50 51 // The resource id for the 'Stats for nerds' link button. 52 const gint kTaskManagerAboutMemoryLink = 2; 53 54 // The resource id for the 'Purge Memory' button 55 const gint kTaskManagerPurgeMemory = 3; 56 57 enum TaskManagerColumn { 58 kTaskManagerIcon, 59 kTaskManagerTask, 60 kTaskManagerProfileName, 61 kTaskManagerSharedMem, 62 kTaskManagerPrivateMem, 63 kTaskManagerCPU, 64 kTaskManagerNetwork, 65 kTaskManagerProcessID, 66 kTaskManagerJavaScriptMemory, 67 kTaskManagerWebCoreImageCache, 68 kTaskManagerWebCoreScriptsCache, 69 kTaskManagerWebCoreCssCache, 70 kTaskManagerVideoMemory, 71 kTaskManagerFPS, 72 kTaskManagerSqliteMemoryUsed, 73 kTaskManagerGoatsTeleported, 74 kTaskManagerColumnCount, 75 }; 76 77 const TaskManagerColumn kTaskManagerLastVisibleColumn = 78 kTaskManagerGoatsTeleported; 79 80 TaskManagerColumn TaskManagerResourceIDToColumnID(int id) { 81 switch (id) { 82 case IDS_TASK_MANAGER_TASK_COLUMN: 83 return kTaskManagerTask; 84 case IDS_TASK_MANAGER_PROFILE_NAME_COLUMN: 85 return kTaskManagerProfileName; 86 case IDS_TASK_MANAGER_SHARED_MEM_COLUMN: 87 return kTaskManagerSharedMem; 88 case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN: 89 return kTaskManagerPrivateMem; 90 case IDS_TASK_MANAGER_CPU_COLUMN: 91 return kTaskManagerCPU; 92 case IDS_TASK_MANAGER_NET_COLUMN: 93 return kTaskManagerNetwork; 94 case IDS_TASK_MANAGER_PROCESS_ID_COLUMN: 95 return kTaskManagerProcessID; 96 case IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN: 97 return kTaskManagerJavaScriptMemory; 98 case IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN: 99 return kTaskManagerWebCoreImageCache; 100 case IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN: 101 return kTaskManagerWebCoreScriptsCache; 102 case IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN: 103 return kTaskManagerWebCoreCssCache; 104 case IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN: 105 return kTaskManagerVideoMemory; 106 case IDS_TASK_MANAGER_FPS_COLUMN: 107 return kTaskManagerFPS; 108 case IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN: 109 return kTaskManagerSqliteMemoryUsed; 110 case IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN: 111 return kTaskManagerGoatsTeleported; 112 default: 113 NOTREACHED(); 114 return static_cast<TaskManagerColumn>(-1); 115 } 116 } 117 118 int TaskManagerColumnIDToResourceID(int id) { 119 switch (id) { 120 case kTaskManagerTask: 121 return IDS_TASK_MANAGER_TASK_COLUMN; 122 case kTaskManagerProfileName: 123 return IDS_TASK_MANAGER_PROFILE_NAME_COLUMN; 124 case kTaskManagerSharedMem: 125 return IDS_TASK_MANAGER_SHARED_MEM_COLUMN; 126 case kTaskManagerPrivateMem: 127 return IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN; 128 case kTaskManagerCPU: 129 return IDS_TASK_MANAGER_CPU_COLUMN; 130 case kTaskManagerNetwork: 131 return IDS_TASK_MANAGER_NET_COLUMN; 132 case kTaskManagerProcessID: 133 return IDS_TASK_MANAGER_PROCESS_ID_COLUMN; 134 case kTaskManagerJavaScriptMemory: 135 return IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN; 136 case kTaskManagerWebCoreImageCache: 137 return IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN; 138 case kTaskManagerWebCoreScriptsCache: 139 return IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN; 140 case kTaskManagerWebCoreCssCache: 141 return IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN; 142 case kTaskManagerVideoMemory: 143 return IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN; 144 case kTaskManagerFPS: 145 return IDS_TASK_MANAGER_FPS_COLUMN; 146 case kTaskManagerSqliteMemoryUsed: 147 return IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN; 148 case kTaskManagerGoatsTeleported: 149 return IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN; 150 default: 151 NOTREACHED(); 152 return -1; 153 } 154 } 155 156 // Should be used for all gtk_tree_view functions that require a column index on 157 // input. 158 // 159 // We need colid - 1 because the gtk_tree_view function is asking for the 160 // column index, not the column id, and both kTaskManagerIcon and 161 // kTaskManagerTask are in the same column index, so all column IDs are off by 162 // one. 163 int TreeViewColumnIndexFromID(TaskManagerColumn colid) { 164 return colid - 1; 165 } 166 167 // Shows or hides a treeview column. 168 void TreeViewColumnSetVisible(GtkWidget* treeview, TaskManagerColumn colid, 169 bool visible) { 170 GtkTreeViewColumn* column = gtk_tree_view_get_column( 171 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid)); 172 gtk_tree_view_column_set_visible(column, visible); 173 } 174 175 bool TreeViewColumnIsVisible(GtkWidget* treeview, TaskManagerColumn colid) { 176 GtkTreeViewColumn* column = gtk_tree_view_get_column( 177 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid)); 178 return gtk_tree_view_column_get_visible(column); 179 } 180 181 // The task column is special because it has an icon and it gets special 182 // treatment with respect to resizing the columns. 183 void TreeViewInsertTaskColumn(GtkWidget* treeview, int resid) { 184 int colid = TaskManagerResourceIDToColumnID(resid); 185 GtkTreeViewColumn* column = gtk_tree_view_column_new(); 186 gtk_tree_view_column_set_title(column, 187 l10n_util::GetStringUTF8(resid).c_str()); 188 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(treeview), colid); 189 GtkCellRenderer* image_renderer = gtk_cell_renderer_pixbuf_new(); 190 gtk_tree_view_column_pack_start(column, image_renderer, FALSE); 191 gtk_tree_view_column_add_attribute(column, image_renderer, 192 "pixbuf", kTaskManagerIcon); 193 GtkCellRenderer* text_renderer = gtk_cell_renderer_text_new(); 194 gtk_tree_view_column_pack_start(column, text_renderer, TRUE); 195 gtk_tree_view_column_add_attribute(column, text_renderer, "markup", colid); 196 gtk_tree_view_column_set_resizable(column, TRUE); 197 // This is temporary: we'll turn expanding off after getting the size. 198 gtk_tree_view_column_set_expand(column, TRUE); 199 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); 200 gtk_tree_view_column_set_sort_column_id(column, colid); 201 } 202 203 // Inserts a column with a column id of |colid| and |name|. 204 void TreeViewInsertColumnWithName(GtkWidget* treeview, 205 TaskManagerColumn colid, const char* name) { 206 GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); 207 gtk_tree_view_insert_column_with_attributes( 208 GTK_TREE_VIEW(treeview), -1, 209 name, renderer, 210 "text", colid, 211 NULL); 212 GtkTreeViewColumn* column = gtk_tree_view_get_column( 213 GTK_TREE_VIEW(treeview), TreeViewColumnIndexFromID(colid)); 214 gtk_tree_view_column_set_resizable(column, TRUE); 215 gtk_tree_view_column_set_sort_column_id(column, colid); 216 } 217 218 // Loads the column name from |resid| and uses the corresponding 219 // TaskManagerColumn value as the column id to insert into the treeview. 220 void TreeViewInsertColumn(GtkWidget* treeview, int resid) { 221 TreeViewInsertColumnWithName(treeview, TaskManagerResourceIDToColumnID(resid), 222 l10n_util::GetStringUTF8(resid).c_str()); 223 } 224 225 // Set the current width of the column without forcing a fixed or maximum 226 // width as gtk_tree_view_column_set_[fixed|maximum]_width() would. This would 227 // basically be gtk_tree_view_column_set_width() except that there is no such 228 // function. It turns out that other applications have done similar hacks to do 229 // the same thing - search the web for that nonexistent function name! :) 230 void TreeViewColumnSetWidth(GtkTreeViewColumn* column, gint width) { 231 column->width = width; 232 column->resized_width = width; 233 column->use_resized_width = TRUE; 234 // Needed for use_resized_width to be effective. 235 gtk_widget_queue_resize(column->tree_view); 236 } 237 238 } // namespace 239 240 class TaskManagerGtk::ContextMenuController 241 : public ui::SimpleMenuModel::Delegate { 242 public: 243 explicit ContextMenuController(TaskManagerGtk* task_manager) 244 : task_manager_(task_manager) { 245 menu_model_.reset(new ui::SimpleMenuModel(this)); 246 for (int i = kTaskManagerTask; i <= kTaskManagerLastVisibleColumn; i++) { 247 menu_model_->AddCheckItemWithStringId( 248 i, TaskManagerColumnIDToResourceID(i)); 249 } 250 menu_.reset(new MenuGtk(NULL, menu_model_.get())); 251 } 252 253 virtual ~ContextMenuController() {} 254 255 void RunMenu(const gfx::Point& point, guint32 event_time) { 256 menu_->PopupAsContext(point, event_time); 257 } 258 259 void Cancel() { 260 task_manager_ = NULL; 261 menu_->Cancel(); 262 } 263 264 private: 265 // ui::SimpleMenuModel::Delegate implementation: 266 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { 267 if (!task_manager_) 268 return false; 269 270 return true; 271 } 272 273 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE { 274 if (!task_manager_) 275 return false; 276 277 TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id); 278 return TreeViewColumnIsVisible(task_manager_->treeview_, colid); 279 } 280 281 virtual bool GetAcceleratorForCommandId( 282 int command_id, 283 ui::Accelerator* accelerator) OVERRIDE { 284 return false; 285 } 286 287 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE { 288 if (!task_manager_) 289 return; 290 291 TaskManagerColumn colid = static_cast<TaskManagerColumn>(command_id); 292 bool visible = !TreeViewColumnIsVisible(task_manager_->treeview_, colid); 293 TreeViewColumnSetVisible(task_manager_->treeview_, colid, visible); 294 } 295 296 // The model and view for the right click context menu. 297 scoped_ptr<ui::SimpleMenuModel> menu_model_; 298 scoped_ptr<MenuGtk> menu_; 299 300 // The TaskManager the context menu was brought up for. Set to NULL when the 301 // menu is canceled. 302 TaskManagerGtk* task_manager_; 303 304 DISALLOW_COPY_AND_ASSIGN(ContextMenuController); 305 }; 306 307 TaskManagerGtk::TaskManagerGtk() 308 : task_manager_(TaskManager::GetInstance()), 309 model_(TaskManager::GetInstance()->model()), 310 dialog_(NULL), 311 treeview_(NULL), 312 process_list_(NULL), 313 process_count_(0), 314 ignore_selection_changed_(false) { 315 Init(); 316 } 317 318 // static 319 TaskManagerGtk* TaskManagerGtk::instance_ = NULL; 320 321 TaskManagerGtk::~TaskManagerGtk() { 322 model_->RemoveObserver(this); 323 task_manager_->OnWindowClosed(); 324 325 gtk_accel_group_disconnect_key(accel_group_, GDK_w, GDK_CONTROL_MASK); 326 gtk_window_remove_accel_group(GTK_WINDOW(dialog_), accel_group_); 327 g_object_unref(accel_group_); 328 accel_group_ = NULL; 329 330 // Disconnect the destroy signal so it doesn't delete |this|. 331 g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_handler_id_); 332 gtk_widget_destroy(dialog_); 333 } 334 335 //////////////////////////////////////////////////////////////////////////////// 336 // TaskManagerGtk, TaskManagerModelObserver implementation: 337 338 void TaskManagerGtk::OnModelChanged() { 339 // Nothing to do. 340 } 341 342 void TaskManagerGtk::OnItemsChanged(int start, int length) { 343 GtkTreeIter iter; 344 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter, 345 NULL, start)) { 346 NOTREACHED() << "Can't get child " << start << 347 " from GTK_TREE_MODEL(process_list_)"; 348 } 349 350 for (int i = start; i < start + length; i++) { 351 SetRowDataFromModel(i, &iter); 352 if (i != start + length - 1) { 353 if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(process_list_), &iter)) { 354 NOTREACHED() << "Can't get next GtkTreeIter object from process_list_ " 355 "iterator at position " << i; 356 } 357 } 358 } 359 } 360 361 void TaskManagerGtk::OnItemsAdded(int start, int length) { 362 base::AutoReset<bool> autoreset(&ignore_selection_changed_, true); 363 364 GtkTreeIter iter; 365 if (start == 0) { 366 gtk_list_store_prepend(process_list_, &iter); 367 } else if (start >= process_count_) { 368 gtk_list_store_append(process_list_, &iter); 369 } else { 370 GtkTreeIter sibling; 371 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &sibling, 372 NULL, start); 373 gtk_list_store_insert_before(process_list_, &iter, &sibling); 374 } 375 376 SetRowDataFromModel(start, &iter); 377 378 for (int i = start + 1; i < start + length; i++) { 379 gtk_list_store_insert_after(process_list_, &iter, &iter); 380 SetRowDataFromModel(i, &iter); 381 } 382 383 process_count_ += length; 384 } 385 386 void TaskManagerGtk::OnItemsRemoved(int start, int length) { 387 { 388 base::AutoReset<bool> autoreset(&ignore_selection_changed_, true); 389 390 GtkTreeIter iter; 391 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(process_list_), &iter, 392 NULL, start); 393 394 for (int i = 0; i < length; i++) { 395 // |iter| is moved to the next valid node when the current node is 396 // removed. 397 gtk_list_store_remove(process_list_, &iter); 398 } 399 400 process_count_ -= length; 401 } 402 403 // It is possible that we have removed the current selection; run selection 404 // changed to detect that case. 405 OnSelectionChanged(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_))); 406 } 407 408 //////////////////////////////////////////////////////////////////////////////// 409 // TaskManagerGtk, public: 410 411 void TaskManagerGtk::Close() { 412 // Blow away our dialog - this will cause TaskManagerGtk to free itself. 413 gtk_widget_destroy(dialog_); 414 DCHECK(!instance_); 415 } 416 417 // static 418 void TaskManagerGtk::Show() { 419 if (instance_) { 420 // If there's a Task manager window open already, just activate it. 421 gtk_util::PresentWindow(instance_->dialog_, 0); 422 } else { 423 instance_ = new TaskManagerGtk(); 424 instance_->model_->StartUpdating(); 425 } 426 } 427 428 //////////////////////////////////////////////////////////////////////////////// 429 // TaskManagerGtk, private: 430 431 void TaskManagerGtk::Init() { 432 dialog_ = gtk_dialog_new_with_buttons( 433 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_TITLE).c_str(), 434 // Task Manager window is shared between all browsers. 435 NULL, 436 GTK_DIALOG_NO_SEPARATOR, 437 NULL); 438 439 // Allow browser windows to go in front of the task manager dialog in 440 // metacity. 441 gtk_window_set_type_hint(GTK_WINDOW(dialog_), GDK_WINDOW_TYPE_HINT_NORMAL); 442 443 if (CommandLine::ForCurrentProcess()->HasSwitch( 444 switches::kPurgeMemoryButton)) { 445 gtk_dialog_add_button(GTK_DIALOG(dialog_), 446 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_PURGE_MEMORY).c_str(), 447 kTaskManagerPurgeMemory); 448 } 449 450 if (browser_defaults::kShowCancelButtonInTaskManager) { 451 gtk_dialog_add_button(GTK_DIALOG(dialog_), 452 l10n_util::GetStringUTF8(IDS_CLOSE).c_str(), 453 GTK_RESPONSE_DELETE_EVENT); 454 } 455 456 gtk_dialog_add_button(GTK_DIALOG(dialog_), 457 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_KILL).c_str(), 458 kTaskManagerResponseKill); 459 460 // The response button should not be sensitive when the dialog is first opened 461 // because the selection is initially empty. 462 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_), 463 kTaskManagerResponseKill, FALSE); 464 465 GtkWidget* link = gtk_chrome_link_button_new( 466 l10n_util::GetStringUTF8(IDS_TASK_MANAGER_ABOUT_MEMORY_LINK).c_str()); 467 gtk_dialog_add_action_widget(GTK_DIALOG(dialog_), link, 468 kTaskManagerAboutMemoryLink); 469 470 // Setting the link widget to secondary positions the button on the left side 471 // of the action area (vice versa for RTL layout). 472 GtkWidget* action_area = gtk_dialog_get_action_area(GTK_DIALOG(dialog_)); 473 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(action_area), link, TRUE); 474 475 ConnectAccelerators(); 476 477 GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_)); 478 gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing); 479 480 destroy_handler_id_ = g_signal_connect(dialog_, "destroy", 481 G_CALLBACK(OnDestroyThunk), this); 482 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this); 483 g_signal_connect(dialog_, "button-press-event", 484 G_CALLBACK(OnButtonEventThunk), this); 485 gtk_widget_add_events(dialog_, 486 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); 487 488 // Wrap the treeview widget in a scrolled window in order to have a frame. 489 GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL); 490 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), 491 GTK_SHADOW_ETCHED_IN); 492 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), 493 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); 494 495 gtk_container_add(GTK_CONTAINER(content_area), scrolled); 496 497 CreateTaskManagerTreeview(); 498 gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview_), TRUE); 499 g_signal_connect(treeview_, "row-activated", 500 G_CALLBACK(OnRowActivatedThunk), this); 501 g_signal_connect(treeview_, "button-press-event", 502 G_CALLBACK(OnButtonEventThunk), this); 503 504 // |selection| is owned by |treeview_|. 505 GtkTreeSelection* selection = gtk_tree_view_get_selection( 506 GTK_TREE_VIEW(treeview_)); 507 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); 508 g_signal_connect(selection, "changed", 509 G_CALLBACK(OnSelectionChangedThunk), this); 510 511 gtk_container_add(GTK_CONTAINER(scrolled), treeview_); 512 513 SetInitialDialogSize(); 514 gtk_util::ShowDialog(dialog_); 515 516 // If the model already has resources, we need to add them before we start 517 // observing events. 518 if (model_->ResourceCount() > 0) 519 OnItemsAdded(0, model_->ResourceCount()); 520 521 model_->AddObserver(this); 522 } 523 524 void TaskManagerGtk::SetInitialDialogSize() { 525 // Hook up to the realize event so we can size the task column to the 526 // size of the leftover space after packing the other columns. 527 g_signal_connect(treeview_, "realize", 528 G_CALLBACK(OnTreeViewRealizeThunk), this); 529 // If we previously saved the dialog's bounds, use them. 530 if (g_browser_process->local_state()) { 531 const DictionaryValue* placement_pref = 532 g_browser_process->local_state()->GetDictionary( 533 prefs::kTaskManagerWindowPlacement); 534 int top = 0, left = 0, bottom = 1, right = 1; 535 if (placement_pref && 536 placement_pref->GetInteger("top", &top) && 537 placement_pref->GetInteger("left", &left) && 538 placement_pref->GetInteger("bottom", &bottom) && 539 placement_pref->GetInteger("right", &right)) { 540 gtk_window_resize(GTK_WINDOW(dialog_), 541 std::max(1, right - left), 542 std::max(1, bottom - top)); 543 return; 544 } 545 } 546 547 // Otherwise, just set a default size (GTK will override this if it's not 548 // large enough to hold the window's contents). 549 gtk_window_set_default_size( 550 GTK_WINDOW(dialog_), kDefaultWidth, kDefaultHeight); 551 } 552 553 void TaskManagerGtk::ConnectAccelerators() { 554 accel_group_ = gtk_accel_group_new(); 555 gtk_window_add_accel_group(GTK_WINDOW(dialog_), accel_group_); 556 557 gtk_accel_group_connect(accel_group_, 558 GDK_w, GDK_CONTROL_MASK, GtkAccelFlags(0), 559 g_cclosure_new(G_CALLBACK(OnGtkAcceleratorThunk), 560 this, NULL)); 561 } 562 563 void TaskManagerGtk::CreateTaskManagerTreeview() { 564 process_list_ = gtk_list_store_new(kTaskManagerColumnCount, 565 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, 566 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, 567 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, 568 G_TYPE_STRING, G_TYPE_STRING); 569 570 // Support sorting on all columns. 571 process_list_sort_ = gtk_tree_model_sort_new_with_model( 572 GTK_TREE_MODEL(process_list_)); 573 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 574 kTaskManagerTask, 575 ComparePage, this, NULL); 576 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 577 kTaskManagerTask, 578 CompareProfileName, this, NULL); 579 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 580 kTaskManagerSharedMem, 581 CompareSharedMemory, this, NULL); 582 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 583 kTaskManagerPrivateMem, 584 ComparePrivateMemory, this, NULL); 585 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 586 kTaskManagerJavaScriptMemory, 587 CompareV8Memory, this, NULL); 588 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 589 kTaskManagerCPU, 590 CompareCPU, this, NULL); 591 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 592 kTaskManagerNetwork, 593 CompareNetwork, this, NULL); 594 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 595 kTaskManagerProcessID, 596 CompareProcessID, this, NULL); 597 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 598 kTaskManagerWebCoreImageCache, 599 CompareWebCoreImageCache, this, NULL); 600 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 601 kTaskManagerWebCoreScriptsCache, 602 CompareWebCoreScriptsCache, this, NULL); 603 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 604 kTaskManagerWebCoreCssCache, 605 CompareWebCoreCssCache, this, NULL); 606 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 607 kTaskManagerVideoMemory, 608 CompareVideoMemory, this, NULL); 609 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 610 kTaskManagerFPS, 611 CompareFPS, this, NULL); 612 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 613 kTaskManagerSqliteMemoryUsed, 614 CompareSqliteMemoryUsed, this, NULL); 615 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(process_list_sort_), 616 kTaskManagerGoatsTeleported, 617 CompareGoatsTeleported, this, NULL); 618 treeview_ = gtk_tree_view_new_with_model(process_list_sort_); 619 620 // Insert all the columns. 621 TreeViewInsertTaskColumn(treeview_, IDS_TASK_MANAGER_TASK_COLUMN); 622 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROFILE_NAME_COLUMN); 623 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SHARED_MEM_COLUMN); 624 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN); 625 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_CPU_COLUMN); 626 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_NET_COLUMN); 627 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_PROCESS_ID_COLUMN); 628 TreeViewInsertColumn(treeview_, 629 IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN); 630 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN); 631 TreeViewInsertColumn(treeview_, 632 IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN); 633 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN); 634 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN); 635 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_FPS_COLUMN); 636 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN); 637 TreeViewInsertColumn(treeview_, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN); 638 639 // Hide some columns by default. 640 TreeViewColumnSetVisible(treeview_, kTaskManagerProfileName, false); 641 TreeViewColumnSetVisible(treeview_, kTaskManagerSharedMem, false); 642 TreeViewColumnSetVisible(treeview_, kTaskManagerJavaScriptMemory, false); 643 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreImageCache, false); 644 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreScriptsCache, false); 645 TreeViewColumnSetVisible(treeview_, kTaskManagerWebCoreCssCache, false); 646 TreeViewColumnSetVisible(treeview_, kTaskManagerVideoMemory, false); 647 TreeViewColumnSetVisible(treeview_, kTaskManagerSqliteMemoryUsed, false); 648 TreeViewColumnSetVisible(treeview_, kTaskManagerGoatsTeleported, false); 649 650 g_object_unref(process_list_); 651 g_object_unref(process_list_sort_); 652 } 653 654 std::string TaskManagerGtk::GetModelText(int row, int col_id) { 655 return UTF16ToUTF8(model_->GetResourceById(row, col_id)); 656 } 657 658 GdkPixbuf* TaskManagerGtk::GetModelIcon(int row) { 659 SkBitmap icon = *model_->GetResourceIcon(row).bitmap(); 660 if (icon.pixelRef() == 661 ui::ResourceBundle::GetSharedInstance().GetImageNamed( 662 IDR_DEFAULT_FAVICON).AsBitmap().pixelRef()) { 663 return static_cast<GdkPixbuf*>(g_object_ref( 664 GtkThemeService::GetDefaultFavicon(true).ToGdkPixbuf())); 665 } 666 667 return gfx::GdkPixbufFromSkBitmap(icon); 668 } 669 670 void TaskManagerGtk::SetRowDataFromModel(int row, GtkTreeIter* iter) { 671 GdkPixbuf* icon = GetModelIcon(row); 672 std::string task = GetModelText(row, IDS_TASK_MANAGER_TASK_COLUMN); 673 std::string profile_name = 674 GetModelText(row, IDS_TASK_MANAGER_PROFILE_NAME_COLUMN); 675 gchar* task_markup = g_markup_escape_text(task.c_str(), task.length()); 676 std::string shared_mem = 677 GetModelText(row, IDS_TASK_MANAGER_SHARED_MEM_COLUMN); 678 std::string priv_mem = GetModelText(row, IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN); 679 std::string cpu = GetModelText(row, IDS_TASK_MANAGER_CPU_COLUMN); 680 std::string net = GetModelText(row, IDS_TASK_MANAGER_NET_COLUMN); 681 std::string procid = GetModelText(row, IDS_TASK_MANAGER_PROCESS_ID_COLUMN); 682 683 // Querying the renderer metrics is slow as it has to do IPC, so only do it 684 // when the columns are visible. 685 std::string javascript_memory; 686 if (TreeViewColumnIsVisible(treeview_, kTaskManagerJavaScriptMemory)) { 687 javascript_memory = 688 GetModelText(row, IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN); 689 } 690 std::string wk_img_cache; 691 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreImageCache)) { 692 wk_img_cache = 693 GetModelText(row, IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN); 694 } 695 std::string wk_scripts_cache; 696 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreScriptsCache)) { 697 wk_scripts_cache = 698 GetModelText(row, IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN); 699 } 700 std::string wk_css_cache; 701 if (TreeViewColumnIsVisible(treeview_, kTaskManagerWebCoreCssCache)) { 702 wk_css_cache = 703 GetModelText(row, IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN); 704 } 705 std::string video_memory; 706 if (TreeViewColumnIsVisible(treeview_, kTaskManagerVideoMemory)) 707 video_memory = GetModelText(row, IDS_TASK_MANAGER_VIDEO_MEMORY_COLUMN); 708 std::string fps; 709 if (TreeViewColumnIsVisible(treeview_, kTaskManagerFPS)) 710 fps = GetModelText(row, IDS_TASK_MANAGER_FPS_COLUMN); 711 std::string sqlite_memory; 712 if (TreeViewColumnIsVisible(treeview_, kTaskManagerSqliteMemoryUsed)) { 713 sqlite_memory = 714 GetModelText(row, IDS_TASK_MANAGER_SQLITE_MEMORY_USED_COLUMN); 715 } 716 717 std::string goats = 718 GetModelText(row, IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN); 719 720 gtk_list_store_set(process_list_, iter, 721 kTaskManagerIcon, icon, 722 kTaskManagerTask, task_markup, 723 kTaskManagerProfileName, profile_name.c_str(), 724 kTaskManagerSharedMem, shared_mem.c_str(), 725 kTaskManagerPrivateMem, priv_mem.c_str(), 726 kTaskManagerCPU, cpu.c_str(), 727 kTaskManagerNetwork, net.c_str(), 728 kTaskManagerProcessID, procid.c_str(), 729 kTaskManagerJavaScriptMemory, javascript_memory.c_str(), 730 kTaskManagerWebCoreImageCache, wk_img_cache.c_str(), 731 kTaskManagerWebCoreScriptsCache, wk_scripts_cache.c_str(), 732 kTaskManagerWebCoreCssCache, wk_css_cache.c_str(), 733 kTaskManagerVideoMemory, video_memory.c_str(), 734 kTaskManagerFPS, fps.c_str(), 735 kTaskManagerSqliteMemoryUsed, sqlite_memory.c_str(), 736 kTaskManagerGoatsTeleported, goats.c_str(), 737 -1); 738 g_object_unref(icon); 739 g_free(task_markup); 740 } 741 742 void TaskManagerGtk::KillSelectedProcesses() { 743 GtkTreeSelection* selection = gtk_tree_view_get_selection( 744 GTK_TREE_VIEW(treeview_)); 745 746 GtkTreeModel* model; 747 GList* paths = gtk_tree_selection_get_selected_rows(selection, &model); 748 for (GList* item = paths; item; item = item->next) { 749 GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path( 750 GTK_TREE_MODEL_SORT(process_list_sort_), 751 reinterpret_cast<GtkTreePath*>(item->data)); 752 int row = gtk_tree::GetRowNumForPath(path); 753 gtk_tree_path_free(path); 754 task_manager_->KillProcess(row); 755 } 756 g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL); 757 g_list_free(paths); 758 } 759 760 void TaskManagerGtk::ShowContextMenu(const gfx::Point& point, 761 guint32 event_time) { 762 if (!menu_controller_.get()) 763 menu_controller_.reset(new ContextMenuController(this)); 764 765 menu_controller_->RunMenu(point, event_time); 766 } 767 768 void TaskManagerGtk::OnLinkActivated() { 769 task_manager_->OpenAboutMemory(chrome::HOST_DESKTOP_TYPE_NATIVE); 770 } 771 772 gint TaskManagerGtk::CompareImpl(GtkTreeModel* model, GtkTreeIter* a, 773 GtkTreeIter* b, int id) { 774 int row1 = gtk_tree::GetRowNumForIter(model, b); 775 int row2 = gtk_tree::GetRowNumForIter(model, a); 776 777 // Otherwise, make sure grouped resources are shown together. 778 TaskManagerModel::GroupRange group_range1 = 779 model_->GetGroupRangeForResource(row1); 780 TaskManagerModel::GroupRange group_range2 = 781 model_->GetGroupRangeForResource(row2); 782 783 if (group_range1 == group_range2) { 784 // Sort within groups. 785 // We want the first-in-group row at the top, whether we are sorting up or 786 // down. 787 GtkSortType sort_type; 788 gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(process_list_sort_), 789 NULL, &sort_type); 790 if (row1 == group_range1.first) 791 return sort_type == GTK_SORT_ASCENDING ? -1 : 1; 792 if (row2 == group_range2.first) 793 return sort_type == GTK_SORT_ASCENDING ? 1 : -1; 794 795 return model_->CompareValues(row1, row2, id); 796 } else { 797 // Sort between groups. 798 // Compare by the first-in-group rows so that the groups will stay together. 799 return model_->CompareValues(group_range1.first, group_range2.first, id); 800 } 801 } 802 803 void TaskManagerGtk::OnDestroy(GtkWidget* dialog) { 804 instance_ = NULL; 805 delete this; 806 } 807 808 void TaskManagerGtk::OnResponse(GtkWidget* dialog, int response_id) { 809 if (response_id == GTK_RESPONSE_DELETE_EVENT) { 810 // Store the dialog's size so we can restore it the next time it's opened. 811 if (g_browser_process->local_state()) { 812 gfx::Rect dialog_bounds = gtk_util::GetDialogBounds(GTK_WIDGET(dialog)); 813 814 DictionaryPrefUpdate update(g_browser_process->local_state(), 815 prefs::kTaskManagerWindowPlacement); 816 DictionaryValue* placement_pref = update.Get(); 817 // Note that we store left/top for consistency with Windows, but that we 818 // *don't* restore them. 819 placement_pref->SetInteger("left", dialog_bounds.x()); 820 placement_pref->SetInteger("top", dialog_bounds.y()); 821 placement_pref->SetInteger("right", dialog_bounds.right()); 822 placement_pref->SetInteger("bottom", dialog_bounds.bottom()); 823 placement_pref->SetBoolean("maximized", false); 824 } 825 826 instance_ = NULL; 827 delete this; 828 } else if (response_id == kTaskManagerResponseKill) { 829 KillSelectedProcesses(); 830 } else if (response_id == kTaskManagerAboutMemoryLink) { 831 OnLinkActivated(); 832 } else if (response_id == kTaskManagerPurgeMemory) { 833 MemoryPurger::PurgeAll(); 834 } 835 } 836 837 void TaskManagerGtk::OnTreeViewRealize(GtkTreeView* treeview) { 838 // Five columns show by default: the task column, the memory column, the CPU 839 // column, the network column, and the FPS column. Initially we set the task 840 // tolumn to take all the extra space, with the other columns being sized to 841 // fit the column names. Here we turn off the expand property of the first 842 // column (to make the table behave sanely when the user resizes it), and set 843 // the effective sizes of all five default columns to the automatically chosen 844 // sizes before any rows are added. This causes them to stay at those sizes 845 // even if the data would overflow, preventing a horizontal scroll bar from 846 // appearing due to the row data. 847 static const TaskManagerColumn dfl_columns[] = {kTaskManagerPrivateMem, 848 kTaskManagerCPU, 849 kTaskManagerNetwork, 850 kTaskManagerFPS}; 851 GtkTreeViewColumn* column = NULL; 852 gint width; 853 for (size_t i = 0; i < arraysize(dfl_columns); ++i) { 854 column = gtk_tree_view_get_column(treeview, 855 TreeViewColumnIndexFromID(dfl_columns[i])); 856 width = gtk_tree_view_column_get_width(column); 857 TreeViewColumnSetWidth(column, width); 858 } 859 // Do the task column separately since it's a little different. 860 column = gtk_tree_view_get_column(treeview, 861 TreeViewColumnIndexFromID(kTaskManagerTask)); 862 width = gtk_tree_view_column_get_width(column); 863 // Turn expanding back off to make resizing columns behave sanely. 864 gtk_tree_view_column_set_expand(column, FALSE); 865 TreeViewColumnSetWidth(column, width); 866 } 867 868 void TaskManagerGtk::OnSelectionChanged(GtkTreeSelection* selection) { 869 if (ignore_selection_changed_) 870 return; 871 base::AutoReset<bool> autoreset(&ignore_selection_changed_, true); 872 873 // The set of groups that should be selected. 874 std::set<TaskManagerModel::GroupRange> ranges; 875 bool selection_contains_browser_process = false; 876 877 GtkTreeModel* model; 878 GList* paths = gtk_tree_selection_get_selected_rows(selection, &model); 879 for (GList* item = paths; item; item = item->next) { 880 GtkTreePath* path = gtk_tree_model_sort_convert_path_to_child_path( 881 GTK_TREE_MODEL_SORT(process_list_sort_), 882 reinterpret_cast<GtkTreePath*>(item->data)); 883 int row = gtk_tree::GetRowNumForPath(path); 884 gtk_tree_path_free(path); 885 if (task_manager_->IsBrowserProcess(row)) 886 selection_contains_browser_process = true; 887 ranges.insert(model_->GetGroupRangeForResource(row)); 888 } 889 g_list_foreach(paths, reinterpret_cast<GFunc>(gtk_tree_path_free), NULL); 890 g_list_free(paths); 891 892 for (std::set<TaskManagerModel::GroupRange>::iterator iter = ranges.begin(); 893 iter != ranges.end(); ++iter) { 894 for (int i = 0; i < iter->second; ++i) { 895 GtkTreePath* child_path = gtk_tree_path_new_from_indices(iter->first + i, 896 -1); 897 GtkTreePath* sort_path = gtk_tree_model_sort_convert_child_path_to_path( 898 GTK_TREE_MODEL_SORT(process_list_sort_), child_path); 899 gtk_tree_selection_select_path(selection, sort_path); 900 gtk_tree_path_free(child_path); 901 gtk_tree_path_free(sort_path); 902 } 903 } 904 905 bool sensitive = (paths != NULL) && !selection_contains_browser_process; 906 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_), 907 kTaskManagerResponseKill, sensitive); 908 } 909 910 void TaskManagerGtk::OnRowActivated(GtkWidget* widget, 911 GtkTreePath* path, 912 GtkTreeViewColumn* column) { 913 GtkTreePath* child_path = gtk_tree_model_sort_convert_path_to_child_path( 914 GTK_TREE_MODEL_SORT(process_list_sort_), path); 915 int row = gtk_tree::GetRowNumForPath(child_path); 916 gtk_tree_path_free(child_path); 917 task_manager_->ActivateProcess(row); 918 } 919 920 gboolean TaskManagerGtk::OnButtonEvent(GtkWidget* widget, 921 GdkEventButton* event) { 922 // GTK does menu on mouse-up while views does menu on mouse-down, 923 // so this function can be called from either signal. 924 if (event->button == 3) { 925 ShowContextMenu(gfx::Point(event->x_root, event->y_root), 926 event->time); 927 return TRUE; 928 } 929 930 return FALSE; 931 } 932 933 gboolean TaskManagerGtk::OnGtkAccelerator(GtkAccelGroup* accel_group, 934 GObject* acceleratable, 935 guint keyval, 936 GdkModifierType modifier) { 937 if (keyval == GDK_w && modifier == GDK_CONTROL_MASK) { 938 // The GTK_RESPONSE_DELETE_EVENT response must be sent before the widget 939 // is destroyed. The deleted object will receive gtk signals otherwise. 940 gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT); 941 } 942 943 return TRUE; 944 } 945 946 namespace chrome { 947 948 // Declared in browser_dialogs.h. 949 void ShowTaskManager(Browser* browser) { 950 TaskManagerGtk::Show(); 951 } 952 953 } // namespace chrome 954