Skip to content

Commit

Permalink
Adds a simple task viewer to mash.
Browse files Browse the repository at this point in the history
Required adding an API to ApplicationManager called ApplicationManagerListener that allows the ApplicationManager to broadcast changes to the set of running applications to a listener (the task viewer).

Also had to fix some crashes in the window server.
http://crbug.com/560515

Review URL: https://codereview.chromium.org/1498313002

Cr-Commit-Position: refs/heads/master@{#364826}
  • Loading branch information
ben authored and Commit bot committed Dec 11, 2015
1 parent 6815c88 commit a853d66
Show file tree
Hide file tree
Showing 22 changed files with 508 additions and 13 deletions.
1 change: 1 addition & 0 deletions mash/example/main/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mojo_native_application("main") {

data_deps = [
"//mash/shell",
"//mash/task_viewer",
"//mash/example/views_examples",
"//mash/example/window_type_launcher",
]
Expand Down
1 change: 1 addition & 0 deletions mash/example/main/main_application_delegate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ void MainApplicationDelegate::Initialize(mojo::ApplicationImpl* app) {
connections_.push_back(app->ConnectToApplication("mojo:views_examples"));
connections_.push_back(
app->ConnectToApplication("exe:window_type_launcher_exe"));
connections_.push_back(app->ConnectToApplication("mojo:task_viewer"));
}

bool MainApplicationDelegate::ConfigureIncomingConnection(
Expand Down
33 changes: 33 additions & 0 deletions mash/task_viewer/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//build/config/ui.gni")
import("//mojo/public/mojo_application.gni")
import("//mojo/public/tools/bindings/mojom.gni")
import("//tools/grit/repack.gni")

mojo_native_application("task_viewer") {
sources = [
"main.cc",
"task_viewer_application_delegate.cc",
"task_viewer_application_delegate.h",
]

deps = [
"//base",
"//mojo/application/public/cpp",
"//mojo/application/public/cpp:sources",
"//mojo/application/public/interfaces",
"//mojo/public/cpp/bindings",
"//mojo/services/tracing/public/cpp",
"//ui/views",
"//ui/views/mus:for_mojo_application",
]

resources = [ "$root_out_dir/views_mus_resources.pak" ]

data_deps = [
"//components/mus",
]
}
13 changes: 13 additions & 0 deletions mash/task_viewer/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "mash/task_viewer/task_viewer_application_delegate.h"
#include "mojo/application/public/cpp/application_runner.h"
#include "mojo/public/c/system/main.h"

MojoResult MojoMain(MojoHandle shell_handle) {
mojo::ApplicationRunner runner(
new mash::task_viewer::TaskViewerApplicationDelegate);
return runner.Run(shell_handle);
}
229 changes: 229 additions & 0 deletions mash/task_viewer/task_viewer_application_delegate.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "mash/task_viewer/task_viewer_application_delegate.h"

#include "base/bind.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "mojo/application/public/cpp/application_connection.h"
#include "mojo/application/public/cpp/application_impl.h"
#include "mojo/application/public/interfaces/application_manager.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "ui/base/models/table_model.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/table/table_view.h"
#include "ui/views/controls/table/table_view_observer.h"
#include "ui/views/mus/aura_init.h"
#include "ui/views/mus/window_manager_connection.h"
#include "ui/views/widget/widget_delegate.h"

namespace mash {
namespace task_viewer {
namespace {

using ListenerRequest =
mojo::InterfaceRequest<mojo::shell::mojom::ApplicationManagerListener>;
using mojo::shell::mojom::ApplicationInfoPtr;

class TaskViewer : public views::WidgetDelegateView,
public ui::TableModel,
public views::ButtonListener,
public mojo::shell::mojom::ApplicationManagerListener {
public:
explicit TaskViewer(ListenerRequest request)
: binding_(this, std::move(request)),
table_view_(nullptr),
table_view_parent_(nullptr),
kill_button_(
new views::LabelButton(this, base::ASCIIToUTF16("Kill Process"))),
observer_(nullptr) {
// We don't want to show an empty UI on startup, so just block until we
// receive the initial set of applications.
binding_.WaitForIncomingMethodCall();

table_view_ = new views::TableView(this, GetColumns(), views::TEXT_ONLY,
false);
set_background(views::Background::CreateStandardPanelBackground());

table_view_parent_ = table_view_->CreateParentIfNecessary();
AddChildView(table_view_parent_);

kill_button_->SetStyle(views::Button::STYLE_BUTTON);
AddChildView(kill_button_);
}
~TaskViewer() override {}

private:
struct ProcessInfo {
std::string url;
uint32_t pid;

ProcessInfo(const std::string url, base::ProcessId pid)
: url(url), pid(pid) {}
};

// Overridden from views::WidgetDelegate:
views::View* GetContentsView() override { return this; }
base::string16 GetWindowTitle() const override {
// TODO(beng): use resources.
return base::ASCIIToUTF16("Tasks");
}

// Overridden from views::View:
void Layout() override {
gfx::Rect bounds = GetLocalBounds();
bounds.Inset(10, 10);

gfx::Size ps = kill_button_->GetPreferredSize();
bounds.set_height(bounds.height() - ps.height() - 10);

kill_button_->SetBounds(bounds.width() - ps.width(),
bounds.bottom() + 10,
ps.width(), ps.height());
table_view_parent_->SetBoundsRect(bounds);
}

// Overridden from ui::TableModel:
int RowCount() override {
return static_cast<int>(processes_.size());
}
base::string16 GetText(int row, int column_id) override {
switch(column_id) {
case 0:
DCHECK(row < static_cast<int>(processes_.size()));
return base::UTF8ToUTF16(processes_[row]->url);
case 1:
DCHECK(row < static_cast<int>(processes_.size()));
return base::IntToString16(processes_[row]->pid);
default:
NOTREACHED();
break;
}
return base::string16();
}
void SetObserver(ui::TableModelObserver* observer) override {
observer_ = observer;
}

// Overridden from views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override {
DCHECK_EQ(sender, kill_button_);
DCHECK_EQ(table_view_->SelectedRowCount(), 1);
int row = table_view_->FirstSelectedRow();
DCHECK(row < static_cast<int>(processes_.size()));
base::Process process = base::Process::Open(processes_[row]->pid);
process.Terminate(9, true);
}

// Overridden from mojo::shell::mojom::ApplicationManagerListener:
void SetRunningApplications(
mojo::Array<ApplicationInfoPtr> applications) override {
// This callback should only be called with an empty model.
DCHECK(processes_.empty());
for (size_t i = 0; i < applications.size(); ++i) {
processes_.push_back(
make_scoped_ptr(new ProcessInfo(applications[i]->url,
applications[i]->pid)));
}
}
void ApplicationStarted(ApplicationInfoPtr application) override {
DCHECK(!ContainsURL(application->url));
// TODO(beng): eventually nothing should be returning invalid process ids.
if (application->pid != base::kNullProcessId)
DCHECK(!ContainsPid(application->pid));
processes_.push_back(
make_scoped_ptr(new ProcessInfo(application->url, application->pid)));
observer_->OnItemsAdded(static_cast<int>(processes_.size()), 1);
}
void ApplicationEnded(uint32_t pid) override {
for (auto it = processes_.begin(); it != processes_.end(); ++it) {
if ((*it)->pid == pid) {
observer_->OnItemsRemoved(static_cast<int>(it - processes_.begin()), 1);
processes_.erase(it);
return;
}
}
NOTREACHED();
}

bool ContainsURL(const std::string& url) const {
for (auto& it : processes_) {
if (it->url == url)
return true;
}
return false;
}
bool ContainsPid(uint32_t pid) const {
for (auto& it : processes_) {
if (it->pid == pid)
return true;
}
return false;
}

static std::vector<ui::TableColumn> GetColumns() {
std::vector<ui::TableColumn> columns;

ui::TableColumn url_column;
url_column.id = 0;
// TODO(beng): use resources.
url_column.title = base::ASCIIToUTF16("URL");
url_column.width = -1;
url_column.percent = 0.8f;
url_column.sortable = true;
columns.push_back(url_column);

ui::TableColumn pid_column;
pid_column.id = 1;
// TODO(beng): use resources.
pid_column.title = base::ASCIIToUTF16("PID");
pid_column.width = 50;
pid_column.sortable = true;
columns.push_back(pid_column);

return columns;
}

mojo::Binding<mojo::shell::mojom::ApplicationManagerListener> binding_;

views::TableView* table_view_;
views::View* table_view_parent_;
views::LabelButton* kill_button_;
ui::TableModelObserver* observer_;

std::vector<scoped_ptr<ProcessInfo>> processes_;

DISALLOW_COPY_AND_ASSIGN(TaskViewer);
};

} // namespace

TaskViewerApplicationDelegate::TaskViewerApplicationDelegate() {}

TaskViewerApplicationDelegate::~TaskViewerApplicationDelegate() {}

void TaskViewerApplicationDelegate::Initialize(mojo::ApplicationImpl* app) {
tracing_.Initialize(app);

aura_init_.reset(new views::AuraInit(app, "views_mus_resources.pak"));
views::WindowManagerConnection::Create(app);

mojo::shell::mojom::ApplicationManagerPtr application_manager;
app->ConnectToService("mojo:shell", &application_manager);

mojo::shell::mojom::ApplicationManagerListenerPtr listener;
ListenerRequest request = GetProxy(&listener);
application_manager->AddListener(std::move(listener));

TaskViewer* task_viewer = new TaskViewer(std::move(request));
views::Widget* window = views::Widget::CreateWindowWithBounds(
task_viewer, gfx::Rect(10, 10, 500, 500));
window->Show();
}

} // namespace task_viewer
} // namespace main
45 changes: 45 additions & 0 deletions mash/task_viewer/task_viewer_application_delegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MASH_TASK_VIEWER_TASK_VIEWER_APPLICATION_DELEGATE_H_
#define MASH_TASK_VIEWER_TASK_VIEWER_APPLICATION_DELEGATE_H_

#include <map>

#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "mojo/application/public/cpp/application_delegate.h"
#include "mojo/services/tracing/public/cpp/tracing_impl.h"

namespace mojo {
class ApplicationConnection;
}

namespace views {
class AuraInit;
}

namespace mash {
namespace task_viewer {

class TaskViewerApplicationDelegate : public mojo::ApplicationDelegate {
public:
TaskViewerApplicationDelegate();
~TaskViewerApplicationDelegate() override;

private:
// mojo::ApplicationDelegate:
void Initialize(mojo::ApplicationImpl* app) override;

mojo::TracingImpl tracing_;
scoped_ptr<views::AuraInit> aura_init_;

DISALLOW_COPY_AND_ASSIGN(TaskViewerApplicationDelegate);
};

} // namespace task_viewer
} // namespace mash

#endif // MASH_TASK_VIEWER_TASK_VIEWER_APPLICATION_DELEGATE_H_
22 changes: 22 additions & 0 deletions mojo/application/public/interfaces/application_manager.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ module mojo.shell.mojom;

import "mojo/application/public/interfaces/shell.mojom";

struct ApplicationInfo {
string url;
string qualifier;
uint32 pid;
};

// Implemented by an application that wishes to be informed when the list of
// running applications changes.
interface ApplicationManagerListener {
// Called once when the listener is added via
// ApplicationManager::AddListener() to provide the initial list of running
// applications that the listener observes changes against.
SetRunningApplications(array<ApplicationInfo> applications);

// Tells the listener about applications starting or ending.
ApplicationStarted(ApplicationInfo application);
ApplicationEnded(uint32 pid);
};

interface ApplicationManager {
// Instructs the ApplicationManager to create an instance for an existing
// process at the other end of |channel|, and perform applicable
Expand All @@ -21,4 +40,7 @@ interface ApplicationManager {
// global BrokerState in the parent mojo_runner process.
RegisterProcessWithBroker(uint32 pid,
handle pipe);

// The listener is removed when the pipe is closed.
AddListener(ApplicationManagerListener listener);
};
4 changes: 4 additions & 0 deletions mojo/runner/host/child_process_host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ void ChildProcessHost::Start() {
base::Bind(&ChildProcessHost::DidStart, weak_factory_.GetWeakPtr()));
}

base::ProcessId ChildProcessHost::GetChildPID() const {
return child_process_.IsValid() ? child_process_.Pid() : base::kNullProcessId;
}

int ChildProcessHost::Join() {
if (controller_) // We use this as a signal that Start was called.
start_child_process_event_.Wait();
Expand Down
2 changes: 2 additions & 0 deletions mojo/runner/host/child_process_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class ChildProcessHost {
// |Start()| was called) when the child has been started (or failed to start).
void Start();

base::ProcessId GetChildPID() const;

// Waits for the child process to terminate, and returns its exit code.
int Join();

Expand Down
Loading

0 comments on commit a853d66

Please sign in to comment.