blob: c4ae4e46dc42cbc520a72acddd137256c41e1280 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// A class handling the IPC connection for the session b/w server and clients.
#include "client/client.h"
#ifdef OS_WIN
#include <Windows.h>
#include <ShellAPI.h>
#else
#include <unistd.h>
#endif // OS_WIN
#include <cstddef>
#include "base/const.h"
#include "base/file_stream.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/process.h"
#include "base/run_level.h"
#include "base/singleton.h"
#include "base/system_util.h"
#include "base/util.h"
#include "base/version.h"
#include "config/config.pb.h"
#include "ipc/ipc.h"
#include "session/commands.pb.h"
#ifdef OS_MACOSX
#include "base/mac_process.h"
#endif // OS_MACOSX
namespace mozc {
namespace client {
namespace {
const char kServerAddress[] = "session"; // name for the IPC connection.
const int kResultBufferSize = 8192 * 32; // size of IPC buffer
const size_t kMaxPlayBackSize = 512; // size of maximum history
#ifdef DEBUG
const int kDefaultTimeout = 100000; // 100 sec for dbg
#else
const int kDefaultTimeout = 30000; // 30 sec for opt
#endif // DEBUG
// Delete Session is called inside the Destructor of Client class.
// To prevent from an application being stalled at the close time,
// we change the timeout of DeleteSession shorter.
// This timeout is only applied in the DeleteSessions command
// called from Destructor. When an application calls DeleteSession
// explicitly, the default timeout is used.
const int kDeleteSessionOnDestructorTimeout = 1000; // 1 sec
} // namespace
Client::Client()
: id_(0),
server_launcher_(new ServerLauncher),
result_(new char[kResultBufferSize]),
timeout_(kDefaultTimeout),
server_status_(SERVER_UNKNOWN),
server_protocol_version_(0),
server_process_id_(0),
last_mode_(commands::DIRECT) {
client_factory_ = IPCClientFactory::GetIPCClientFactory();
}
Client::~Client() {
set_timeout(kDeleteSessionOnDestructorTimeout);
DeleteSession();
}
void Client::SetIPCClientFactory(IPCClientFactoryInterface *client_factory) {
client_factory_ = client_factory;
}
void Client::SetServerLauncher(
ServerLauncherInterface *server_launcher) {
server_launcher_.reset(server_launcher);
}
bool Client::IsValidRunLevel() const {
return RunLevel::IsValidClientRunLevel();
}
bool Client::EnsureConnection() {
switch (server_status_) {
case SERVER_OK:
case SERVER_INVALID_SESSION:
return true;
break;
case SERVER_FATAL:
// once the current status goes into SERVER_FATAL. do nothing.
return false;
break;
case SERVER_TIMEOUT:
OnFatal(ServerLauncherInterface::SERVER_TIMEOUT);
server_status_ = SERVER_FATAL;
return false;
break;
case SERVER_BROKEN_MESSAGE:
OnFatal(ServerLauncherInterface::SERVER_BROKEN_MESSAGE);
server_status_ = SERVER_FATAL;
return false;
break;
case SERVER_VERSION_MISMATCH:
OnFatal(ServerLauncherInterface::SERVER_VERSION_MISMATCH);
server_status_ = SERVER_FATAL;
return false;
break;
case SERVER_SHUTDOWN:
#ifdef DEBUG
OnFatal(ServerLauncherInterface::SERVER_SHUTDOWN);
// don't break here as SERVER_SHUTDOWN and SERVER_UNKNOWN
// have basically the same treatment.
#endif // DEBUG
case SERVER_UNKNOWN:
if (StartServer()) {
server_status_ = SERVER_INVALID_SESSION;
return true;
} else {
LOG(ERROR) << "Cannot start server";
OnFatal(ServerLauncherInterface::SERVER_FATAL);
server_status_ = SERVER_FATAL;
return false;
}
break;
default:
LOG(ERROR) << "Unknown status: " << server_status_;
break;
}
return true;
}
bool Client::EnsureSession() {
if (!EnsureConnection()) {
return false;
}
if (server_status_ == SERVER_INVALID_SESSION) {
if (CreateSession()) {
server_status_ = SERVER_OK;
return true;
} else {
LOG(ERROR) << "CreateSession failed";
// call EnsureConnection to display error message
EnsureConnection();
return false;
}
}
return true;
}
void Client::DumpQueryOfDeath() {
LOG(ERROR) << "The playback history looks like a query of death";
const char kFilename[] = "query_of_death.log";
const char kLabel[] = "Query of Death";
DumpHistorySnapshot(kFilename, kLabel);
ResetHistory();
}
void Client::DumpHistorySnapshot(const string &filename,
const string &label) const {
const string snapshot_file =
FileUtil::JoinPath(SystemUtil::GetUserProfileDirectory(), filename);
// open with append mode
OutputFileStream output(snapshot_file.c_str(), ios::app);
output << "---- Start history snapshot for " << label << endl;
output << "Created at " << Logging::GetLogMessageHeader() << endl;
output << "Version " << Version::GetMozcVersion() << endl;
for (size_t i = 0; i < history_inputs_.size(); ++i) {
output << history_inputs_[i].DebugString();
}
output << "---- End history snapshot for " << label << endl;
}
void Client::PlaybackHistory() {
if (history_inputs_.size() >= kMaxPlayBackSize) {
ResetHistory();
return;
}
commands::Output output;
VLOG(1) << "Playback history: size=" << history_inputs_.size();
for (size_t i = 0; i < history_inputs_.size(); ++i) {
history_inputs_[i].set_id(id_);
if (!Call(history_inputs_[i], &output)) {
LOG(ERROR) << "playback history failed: "
<< history_inputs_[i].DebugString();
break;
}
}
}
void Client::PushHistory(const commands::Input &input,
const commands::Output &output) {
if (!output.has_consumed() || !output.consumed()) {
// Do not remember unconsumed input.
return;
}
// Update mode
if (output.has_mode()) {
last_mode_ = output.mode();
}
// don't insert a new input when history_inputs_.size()
// reaches to the maximum size. This prevents DOS attack.
if (history_inputs_.size() < kMaxPlayBackSize) {
history_inputs_.push_back(input);
}
// found context boundary.
// don't regard the empty output (output without preedit) as the context
// boundary, as the IMEOn command make the empty output.
if (input.type() == commands::Input::SEND_KEY &&
output.has_result()) {
ResetHistory();
}
}
// Clear the history and push IMEOn command for initialize session.
void Client::ResetHistory() {
history_inputs_.clear();
#if defined(OS_MACOSX)
// On Mac, we should send ON key at the first of each input session
// excepting the very first session, because when the session is restored,
// its state is direct. On the first session, users should send ON key
// by themselves.
// On Windows, this is not required because now we can send IME On/Off
// state with the key event. See b/8601275
// Note that we are assuming that ResetHistory is called only when the
// client is ON.
// TODO(toshiyuki): Make sure that this assuming is reasonable or not.
if (last_mode_ != commands::DIRECT) {
commands::Input input;
input.set_type(commands::Input::SEND_KEY);
input.mutable_key()->set_special_key(commands::KeyEvent::ON);
input.mutable_key()->set_mode(last_mode_);
history_inputs_.push_back(input);
}
#endif
}
void Client::GetHistoryInputs(vector<commands::Input> *output) const {
output->clear();
for (size_t i = 0; i < history_inputs_.size(); ++i) {
output->push_back(history_inputs_[i]);
}
}
bool Client::SendKeyWithContext(const commands::KeyEvent &key,
const commands::Context &context,
commands::Output *output) {
commands::Input input;
input.set_type(commands::Input::SEND_KEY);
input.mutable_key()->CopyFrom(key);
// If the pointer of |context| is not the default_instance, update the data.
if (&context != &commands::Context::default_instance()) {
input.mutable_context()->CopyFrom(context);
}
return EnsureCallCommand(&input, output);
}
bool Client::TestSendKeyWithContext(const commands::KeyEvent &key,
const commands::Context &context,
commands::Output *output) {
commands::Input input;
input.set_type(commands::Input::TEST_SEND_KEY);
// If the pointer of |context| is not the default_instance, update the data.
if (&context != &commands::Context::default_instance()) {
input.mutable_context()->CopyFrom(context);
}
input.mutable_key()->CopyFrom(key);
return EnsureCallCommand(&input, output);
}
bool Client::SendCommandWithContext(const commands::SessionCommand &command,
const commands::Context &context,
commands::Output *output) {
commands::Input input;
input.set_type(commands::Input::SEND_COMMAND);
input.mutable_command()->CopyFrom(command);
// If the pointer of |context| is not the default_instance, update the data.
if (&context != &commands::Context::default_instance()) {
input.mutable_context()->CopyFrom(context);
}
return EnsureCallCommand(&input, output);
}
bool Client::CheckVersionOrRestartServer() {
commands::Input input;
commands::Output output;
input.set_type(commands::Input::NO_OPERATION);
if (!CheckVersionOrRestartServerInternal(input, &output)) {
LOG(ERROR) << "CheckVersionOrRestartServerInternal failed";
if (!EnsureConnection()) {
LOG(ERROR) << "EnsureConnection failed";
return false;
}
}
return true;
}
bool Client::EnsureCallCommand(commands::Input *input,
commands::Output *output) {
if (!EnsureSession()) {
LOG(ERROR) << "EnsureSession failed";
return false;
}
InitInput(input);
output->set_id(0);
if (!CallAndCheckVersion(*input, output)) { // server is not running
LOG(ERROR) << "Call command failed";
} else if (output->id() != input->id()) { // invalid ID
LOG(ERROR) << "Session id is void. re-issue session id";
server_status_ = SERVER_INVALID_SESSION;
}
// see the result of Call
if (server_status_ >= SERVER_TIMEOUT) {
return false;
}
if (server_status_ == SERVER_SHUTDOWN ||
server_status_ == SERVER_INVALID_SESSION) {
if (EnsureSession()) {
// playback the history to restore the previous state.
PlaybackHistory();
InitInput(input);
#ifdef DEBUG
// The debug binary dumps query of death at the first trial.
history_inputs_.push_back(*input);
DumpQueryOfDeath();
#endif // DEBUG
// second trial
if (!CallAndCheckVersion(*input, output)) {
#ifndef DEBUG
// if second trial failed, record the input
history_inputs_.push_back(*input);
// Opt or release binaries refrain from dumping query of death
// at the first trial, but dumps it at the second trial.
//
// TODO(komatsu, taku): Should release binary dump query of death?
DumpQueryOfDeath();
#endif // DEBUG
return false;
}
} else {
LOG(ERROR) << "EnsureSession failed: " << server_status_;
return false;
}
}
PushHistory(*input, *output);
return true;
}
void Client::EnableCascadingWindow(const bool enable) {
if (preferences_.get() == NULL) {
preferences_.reset(new config::Config);
}
preferences_->set_use_cascading_window(enable);
}
void Client::set_timeout(int timeout) {
timeout_ = timeout;
}
void Client::set_restricted(bool restricted) {
server_launcher_->set_restricted(restricted);
}
void Client::set_server_program(const string &program_path) {
server_launcher_->set_server_program(program_path);
}
void Client::set_suppress_error_dialog(bool suppress) {
server_launcher_->set_suppress_error_dialog(suppress);
}
void Client::set_client_capability(const commands::Capability &capability) {
client_capability_.CopyFrom(capability);
}
bool Client::CreateSession() {
id_ = 0;
commands::Input input;
input.set_type(commands::Input::CREATE_SESSION);
input.mutable_capability()->CopyFrom(client_capability_);
commands::ApplicationInfo *info = input.mutable_application_info();
DCHECK(info);
#ifdef OS_WIN
info->set_process_id(static_cast<uint32>(::GetCurrentProcessId()));
info->set_thread_id(static_cast<uint32>(::GetCurrentThreadId()));
#else
info->set_process_id(static_cast<uint32>(getpid()));
info->set_thread_id(0);
#endif
commands::Output output;
if (!CheckVersionOrRestartServerInternal(input, &output)) {
LOG(ERROR) << "CheckVersionOrRestartServer() failed";
return false;
}
if (output.error_code() != commands::Output::SESSION_SUCCESS) {
LOG(ERROR) << "Server returns an error";
server_status_ = SERVER_INVALID_SESSION;
return false;
}
id_ = output.id();
return true;
}
bool Client::DeleteSession() {
// No need to delete session
if (id_ == 0) {
return true;
}
commands::Input input;
InitInput(&input);
input.set_type(commands::Input::DELETE_SESSION);
commands::Output output;
if (!Call(input, &output)) {
LOG(ERROR) << "DeleteSession failed";
return false;
}
id_ = 0;
return true;
}
bool Client::GetConfig(config::Config *config) {
commands::Input input;
InitInput(&input);
input.set_type(commands::Input::GET_CONFIG);
commands::Output output;
if (!Call(input, &output)) {
return false;
}
if (!output.has_config()) {
return false;
}
config->Clear();
config->CopyFrom(output.config());
return true;
}
bool Client::SetConfig(const config::Config &config) {
commands::Input input;
InitInput(&input);
input.set_type(commands::Input::SET_CONFIG);
input.mutable_config()->CopyFrom(config);
commands::Output output;
if (!Call(input, &output)) {
return false;
}
return true;
}
bool Client::ClearUserHistory() {
return CallCommand(commands::Input::CLEAR_USER_HISTORY);
}
bool Client::ClearUserPrediction() {
return CallCommand(commands::Input::CLEAR_USER_PREDICTION);
}
bool Client::ClearUnusedUserPrediction() {
return CallCommand(commands::Input::CLEAR_UNUSED_USER_PREDICTION);
}
bool Client::Shutdown() {
CallCommand(commands::Input::SHUTDOWN);
if (!server_launcher_->WaitServer(server_process_id_)) {
LOG(ERROR) << "Cannot shutdown the server";
return false;
}
return true;
}
bool Client::SyncData() {
return CallCommand(commands::Input::SYNC_DATA);
}
bool Client::Reload() {
return CallCommand(commands::Input::RELOAD);
}
bool Client::Cleanup() {
return CallCommand(commands::Input::CLEANUP);
}
bool Client::NoOperation() {
return CallCommand(commands::Input::NO_OPERATION);
}
// PingServer ignores all server status
bool Client::PingServer() const {
if (client_factory_ == NULL) {
return false;
}
commands::Input input;
commands::Output output;
InitInput(&input);
input.set_type(commands::Input::NO_OPERATION);
// Call IPC
scoped_ptr<IPCClientInterface> client(
client_factory_->NewClient(kServerAddress,
server_launcher_->server_program()));
if (client.get() == NULL) {
LOG(ERROR) << "Cannot make client object";
return false;
}
if (!client->Connected()) {
LOG(ERROR) << "Connection failure to " << kServerAddress;
return false;
}
// Serialize
string request;
input.SerializeToString(&request);
size_t size = kResultBufferSize;
if (!client->Call(request.data(), request.size(),
result_.get(), &size, timeout_)) {
LOG(ERROR) << "IPCClient::Call failed: " << client->GetLastIPCError();
return false;
}
return true;
}
bool Client::CallCommand(commands::Input::CommandType type) {
commands::Input input;
InitInput(&input);
input.set_type(type);
commands::Output output;
return Call(input, &output);
}
bool Client::CallAndCheckVersion(const commands::Input &input,
commands::Output *output) {
if (!Call(input, output)) {
if (server_protocol_version_ != IPC_PROTOCOL_VERSION) {
LOG(ERROR) << "version mismatch: "
<< server_protocol_version_ << " "
<< static_cast<int>(IPC_PROTOCOL_VERSION);
server_status_ = SERVER_VERSION_MISMATCH;
}
return false;
}
return true;
}
bool Client::Call(const commands::Input &input,
commands::Output *output) {
VLOG(2) << "commands::Input: " << endl << input.DebugString();
// don't repeat Call() if the status is either
// SERVER_FATAL, SERVER_TIMEOUT, or SERVER_BROKEN_MESSAGE
if (server_status_ >= SERVER_TIMEOUT) {
LOG(ERROR) << "Don't repat the same status: " << server_status_;
return false;
}
if (client_factory_ == NULL) {
return false;
}
// Serialize
string request;
input.SerializeToString(&request);
// Call IPC
scoped_ptr<IPCClientInterface> client(
client_factory_->NewClient(kServerAddress,
server_launcher_->server_program()));
// set client protocol version.
// When an error occurs inside Connected() function,
// the server_protocol_version_ may be set to
// the default value defined in .proto file.
// This caused an mis-version-detection.
// To avoid such situation, we set the client protocol version
// before calling IPC request.
server_protocol_version_ = IPC_PROTOCOL_VERSION;
server_product_version_ = Version::GetMozcVersion();
server_process_id_ = 0;
if (client.get() == NULL) {
LOG(ERROR) << "Cannot make client object";
server_status_ = SERVER_FATAL;
return false;
}
if (!client->Connected()) {
LOG(ERROR) << "Connection failure to " << kServerAddress;
// if the status is not SERVER_UNKNOWN, it means that
// the server WAS working as correctly.
if (server_status_ != SERVER_UNKNOWN) {
server_status_ = SERVER_SHUTDOWN;
}
return false;
}
server_protocol_version_ = client->GetServerProtocolVersion();
server_product_version_ = client->GetServerProductVersion();
server_process_id_ = client->GetServerProcessId();
if (server_protocol_version_ != IPC_PROTOCOL_VERSION) {
LOG(ERROR) << "Server version mismatch. skipped to update the status here";
return false;
}
// Drop DebugString() as it raises segmentation fault.
// http://b/2126375
// TODO(taku): Investigate the error in detail.
size_t size = kResultBufferSize;
if (!client->Call(request.data(), request.size(),
result_.get(), &size, timeout_)) {
LOG(ERROR) << "Call failure";
// << input.DebugString();
if (client->GetLastIPCError() == IPC_TIMEOUT_ERROR) {
server_status_ = SERVER_TIMEOUT;
} else {
// server crash
server_status_ = SERVER_SHUTDOWN;
}
return false;
}
if (!output->ParseFromArray(result_.get(), size)) {
LOG(ERROR) << "Parse failure of the result of the request:";
// << input.DebugString();
server_status_ = SERVER_BROKEN_MESSAGE;
return false;
}
DCHECK(server_status_ == SERVER_OK ||
server_status_ == SERVER_INVALID_SESSION ||
server_status_ == SERVER_SHUTDOWN ||
server_status_ == SERVER_UNKNOWN /* during StartServer() */)
<< " " << server_status_;
VLOG(2) << "commands::Output: " << endl << output->DebugString();
return true;
}
bool Client::StartServer() {
if (server_launcher_.get() != NULL) {
return server_launcher_->StartServer(this);
}
return true;
}
void Client::OnFatal(ServerLauncherInterface::ServerErrorType type) {
if (server_launcher_.get() != NULL) {
server_launcher_->OnFatal(type);
}
}
void Client::InitInput(commands::Input *input) const {
input->set_id(id_);
if (preferences_.get() != NULL) {
input->mutable_config()->CopyFrom(*preferences_);
}
}
bool Client::CheckVersionOrRestartServerInternal(
const commands::Input &input, commands::Output *output) {
for (int trial = 0; trial < 2; ++trial) {
const bool call_result = Call(input, output);
if (!call_result && server_protocol_version_ > IPC_PROTOCOL_VERSION) {
LOG(ERROR) << "Server version is newer than client version.";
server_status_ = SERVER_VERSION_MISMATCH;
return false;
}
const bool version_upgraded =
Version::CompareVersion(server_product_version_,
Version::GetMozcVersion());
// if the server version is older than client version or
// protocol version is updated, force to reboot the server.
// if the version is even unchanged after the reboot, goes to
// SERVER_VERSION_MISMATCH state, which brings the client into
// SERVER_FATAL state finally.
if ((call_result && version_upgraded) ||
(!call_result && server_protocol_version_ < IPC_PROTOCOL_VERSION)) {
LOG(WARNING) << "Version Mismatch: "
<< server_product_version_ << " "
<< Version::GetMozcVersion()
<< " "
<< server_protocol_version_ << " "
<< static_cast<int>(IPC_PROTOCOL_VERSION)
<< " " << trial;
if (trial > 0) {
LOG(ERROR) << "Server version mismatch even after server reboot";
server_status_ = SERVER_BROKEN_MESSAGE;
return false;
}
bool shutdown_result = true;
if (call_result && version_upgraded) {
// use shutdown command if the protocol version is compatible
shutdown_result = Shutdown();
if (!shutdown_result) {
LOG(ERROR) << "Shutdown command failed";
}
}
// force to terminate the process if protocol version is not compatible
if (!shutdown_result ||
(!call_result && server_protocol_version_ < IPC_PROTOCOL_VERSION)) {
if (!server_launcher_->ForceTerminateServer(kServerAddress)) {
LOG(ERROR) << "ForceTerminateProcess failed";
server_status_ = SERVER_BROKEN_MESSAGE;
return false;
}
if (!server_launcher_->WaitServer(server_process_id_)) {
LOG(ERROR) << "Cannot terminate server process";
}
}
server_status_ = SERVER_UNKNOWN;
if (!EnsureConnection()) {
server_status_ = SERVER_VERSION_MISMATCH;
LOG(ERROR) << "Ensure Connecion failed";
return false;
}
continue;
}
if (!call_result) {
LOG(ERROR) << "Call() failed";
return false;
}
return true;
}
return false;
}
void Client::Reset() {
server_status_ = SERVER_UNKNOWN;
server_protocol_version_ = 0;
server_process_id_ = 0;
}
bool Client::TranslateProtoBufToMozcToolArg(const commands::Output &output,
string *mode) {
if (!output.has_launch_tool_mode() || mode == NULL) {
return false;
}
switch (output.launch_tool_mode()) {
case commands::Output::CONFIG_DIALOG:
mode->assign("config_dialog");
break;
case commands::Output::DICTIONARY_TOOL:
mode->assign("dictionary_tool");
break;
case commands::Output::WORD_REGISTER_DIALOG:
mode->assign("word_register_dialog");
break;
case commands::Output::NO_TOOL:
default:
// do nothing
return false;
break;
}
return true;
}
bool Client::LaunchToolWithProtoBuf(const commands::Output &output) {
string mode;
if (!TranslateProtoBufToMozcToolArg(output, &mode)) {
return false;
}
// TODO(nona): extends output message to support extra argument.
return LaunchTool(mode, "");
}
bool Client::LaunchTool(const string &mode, const string &extra_arg) {
// Don't execute any child process if the parent process is not
// in proper runlevel.
if (!IsValidRunLevel()) {
return false;
}
// Validate |mode|.
// TODO(taku): better to validate the parameter more carefully.
const size_t kModeMaxSize = 32;
if (mode.empty() || mode.size() >= kModeMaxSize) {
LOG(ERROR) << "Invalid mode: " << mode;
return false;
}
if (mode == "administration_dialog") {
#ifdef OS_WIN
const string &path = mozc::SystemUtil::GetToolPath();
wstring wpath;
Util::UTF8ToWide(path.c_str(), &wpath);
wpath = L"\"" + wpath + L"\"";
// Run administration dialog with UAC.
// AFAIK, ShellExecute is only the way to launch process with
// UAC protection.
// No COM operations are executed as ShellExecute is only
// used for launching an UAC-process.
//
// In Windows XP, cannot use "runas", instead, administration
// dialog is launched with normal process with "open"
// http://b/2415191
const int result =
reinterpret_cast<int>(::ShellExecute(0,
L"runas",
wpath.c_str(),
L"--mode=administration_dialog",
SystemUtil::GetSystemDir(),
SW_SHOW));
if (result <= 32) {
LOG(ERROR) << "::ShellExecute failed: " << result;
return false;
}
#endif // OS_WIN
return false;
}
#if defined(OS_WIN) || defined(OS_LINUX)
string arg = "--mode=" + mode;
if (!extra_arg.empty()) {
arg += " ";
arg += extra_arg;
}
if (!mozc::Process::SpawnMozcProcess(kMozcTool, arg)) {
LOG(ERROR) << "Cannot execute: " << kMozcTool << " " << arg;
return false;
}
#endif // OS_WIN || OS_LINUX
// TODO(taku): move MacProcess inside SpawnMozcProcess.
// TODO(taku): support extra_arg.
#ifdef OS_MACOSX
if (!MacProcess::LaunchMozcTool(mode)) {
LOG(ERROR) << "Cannot execute: " << mode;
return false;
}
#endif // OS_MACOSX
return true;
}
bool Client::OpenBrowser(const string &url) {
if (!IsValidRunLevel()) {
return false;
}
if (!Process::OpenBrowser(url)) {
LOG(ERROR) << "Process::OpenBrowser failed.";
return false;
}
return true;
}
namespace {
class DefaultClientFactory : public ClientFactoryInterface {
public:
virtual ClientInterface *NewClient() {
return new Client;
}
};
ClientFactoryInterface *g_client_factory = NULL;
} // namespace
ClientInterface *ClientFactory::NewClient() {
if (g_client_factory == NULL) {
return Singleton<DefaultClientFactory>::get()->NewClient();
} else {
return g_client_factory->NewClient();
}
}
void ClientFactory::SetClientFactory(
ClientFactoryInterface *client_factory) {
g_client_factory = client_factory;
}
} // namespace client
} // namespace mozc