// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/events/ozone/evdev/libgestures_glue/gesture_feedback.h"
#include <stddef.h>
#include <time.h>
#include <utility>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "build/chromeos_buildflags.h"
#include "ui/events/ozone/evdev/libgestures_glue/gesture_property_provider.h"
namespace ui {
namespace {
// Binary paths.
const char kGzipCommand[] = "/bin/gzip";
const size_t kTouchLogTimestampMaxSize = 80;
// Return the values in an array in one string. Used for touch logging.
template <typename T>
std::string DumpArrayProperty(const std::vector<T>& value, const char* format) {
std::string ret;
for (size_t i = 0; i < value.size(); ++i) {
if (i > 0)
ret.append(", ");
ret.append(base::StringPrintfNonConstexpr(format, value[i]));
}
return ret;
}
// Return the values in a gesture property in one string. Used for touch
// logging.
std::string DumpGesturePropertyValue(GesturesProp* property) {
switch (property->type()) {
case GesturePropertyProvider::PT_INT:
return DumpArrayProperty(property->GetIntValue(), "%d");
case GesturePropertyProvider::PT_SHORT:
return DumpArrayProperty(property->GetShortValue(), "%d");
case GesturePropertyProvider::PT_BOOL:
return DumpArrayProperty(property->GetBoolValue(), "%d");
case GesturePropertyProvider::PT_STRING:
return "\"" + property->GetStringValue() + "\"";
case GesturePropertyProvider::PT_REAL:
return DumpArrayProperty(property->GetDoubleValue(), "%lf");
default:
NOTREACHED_IN_MIGRATION();
break;
}
return std::string();
}
// Compress dumped event logs in place.
void CompressDumpedLog(std::unique_ptr<std::vector<std::string>> log_paths) {
for (size_t i = 0; i < log_paths->size(); ++i) {
// Zip the file.
base::CommandLine command = base::CommandLine(base::FilePath(kGzipCommand));
command.AppendArg("-f");
command.AppendArg((*log_paths)[i]);
std::string output;
base::GetAppOutput(command, &output);
// Replace the original file with the zipped one.
base::Move(base::FilePath((*log_paths)[i] + ".gz"),
base::FilePath((*log_paths)[i]));
}
}
// Get the current time in a string.
std::string GetCurrentTimeForLogging() {
time_t rawtime;
struct tm timeinfo;
char buffer[kTouchLogTimestampMaxSize];
time(&rawtime);
if (!localtime_r(&rawtime, &timeinfo)) {
PLOG(ERROR) << "localtime_r failed";
return "";
}
if (!strftime(buffer, kTouchLogTimestampMaxSize, "%Y%m%d-%H%M%S", &timeinfo))
return "";
return std::string(buffer);
}
// Canonize the device name for logging.
std::string GetCanonicalDeviceName(const std::string& name) {
std::string ret(name);
for (size_t i = 0; i < ret.size(); ++i)
if (!base::IsAsciiAlpha(ret[i]))
ret[i] = '_';
return ret;
}
// Name event logs in a way that is compatible with existing toolchain.
std::string GenerateEventLogName(const base::FilePath& out_dir,
const std::string& prefix,
const std::string& now,
int id) {
return out_dir.value() + "/" + prefix + now + "." + base::NumberToString(id);
}
// Name event logs in a way that is compatible with existing toolchain.
std::string GenerateEventLogName(GesturePropertyProvider* provider,
const base::FilePath& out_dir,
const std::string& prefix,
const std::string& now,
int id) {
return out_dir.value() + "/" + prefix + now + "." + base::NumberToString(id) +
"." + GetCanonicalDeviceName(provider->GetDeviceNameById(id));
}
// Set the logging properties to dump event logs.
void StartToDumpEventLog(GesturePropertyProvider* provider,
const int device_id) {
// Dump gesture log.
GesturesProp* property = provider->GetProperty(device_id, "Log Path");
property->SetStringValue(kTouchpadGestureLogPath);
property = provider->GetProperty(device_id, "Logging Notify");
property->SetIntValue(std::vector<int>(1, 1));
// Dump evdev log.
property = provider->GetProperty(device_id, "Dump Debug Log");
property->SetBoolValue(std::vector<bool>(1, true));
}
} // namespace
// Dump touch device property values to a string.
void DumpTouchDeviceStatus(GesturePropertyProvider* provider,
std::string* status) {
// We use DT_ALL since we want gesture property values for all devices that
// run with the gesture library, not just mice or touchpads.
std::vector<int> ids;
provider->GetDeviceIdsByType(DT_ALL, &ids);
// Dump the property names and values for each device.
for (size_t i = 0; i < ids.size(); ++i) {
std::vector<std::string> names = provider->GetPropertyNamesById(ids[i]);
status->append("\n");
status->append(base::StringPrintf("ID %d:\n", ids[i]));
status->append(base::StringPrintf(
"Device \'%s\':\n", provider->GetDeviceNameById(ids[i]).c_str()));
// Note that, unlike X11, we don't maintain the "atom" concept here.
// Therefore, the property name indices we output here shouldn't be treated
// as unique identifiers of the properties.
std::sort(names.begin(), names.end());
for (size_t j = 0; j < names.size(); ++j) {
status->append(base::StringPrintf("\t%s (%zu):", names[j].c_str(), j));
GesturesProp* property = provider->GetProperty(ids[i], names[j]);
status->append("\t" + DumpGesturePropertyValue(property) + '\n');
}
}
}
// Dump touch event logs.
void DumpTouchEventLog(
const std::map<base::FilePath, std::unique_ptr<EventConverterEvdev>>&
converters,
GesturePropertyProvider* provider,
const base::FilePath& out_dir,
InputController::GetTouchEventLogReply reply) {
std::vector<base::FilePath> log_paths;
// Get device ids.
std::vector<int> ids;
provider->GetDeviceIdsByType(DT_ALL, &ids);
// Get current time stamp.
std::string now = GetCurrentTimeForLogging();
// Dump event logs for gesture devices.
std::unique_ptr<std::vector<std::string>> log_paths_to_be_compressed(
new std::vector<std::string>);
for (size_t i = 0; i < ids.size(); ++i) {
// First, see if the device actually uses the gesture library by checking
// if it has any gesture property.
std::vector<std::string> names = provider->GetPropertyNamesById(ids[i]);
if (names.size() == 0)
continue;
// Set the logging properties to dump event logs. This needs to be done
// synchronously for now or we might have race conditions on the debug
// buffer. If the performance becomes a concern then, we can fork and
// synchronize it.
//
// TODO(sheckylin): Make sure this has no performance impact for user
// feedbacks.
StartToDumpEventLog(provider, ids[i]);
// Rename/move the file to another place since each device's log is
// always dumped using the same name.
std::string gesture_log_filename = GenerateEventLogName(
provider, out_dir, "touchpad_activity_", now, ids[i]);
base::Move(base::FilePath(kTouchpadGestureLogPath),
base::FilePath(gesture_log_filename));
std::string evdev_log_filename = GenerateEventLogName(
provider, out_dir, "cmt_input_events_", now, ids[i]);
base::Move(base::FilePath(kTouchpadEvdevLogPath),
base::FilePath(evdev_log_filename));
// Historically, we compress touchpad/mouse logs with gzip before tarring
// them up. We DONT compress touchscreen logs though.
log_paths_to_be_compressed->push_back(gesture_log_filename);
log_paths.push_back(base::FilePath(gesture_log_filename));
log_paths_to_be_compressed->push_back(evdev_log_filename);
log_paths.push_back(base::FilePath(evdev_log_filename));
}
for (const auto& converter_pair : converters) {
EventConverterEvdev* converter = converter_pair.second.get();
if (converter->HasTouchscreen()) {
std::string touch_evdev_log_filename = GenerateEventLogName(
out_dir, "evdev_input_events_", now, converter->id());
#if BUILDFLAG(IS_CHROMEOS_ASH)
converter->DumpTouchEventLog(touch_evdev_log_filename.c_str());
#else
converter->DumpTouchEventLog(kInputEventsLogFile);
base::Move(base::FilePath(kInputEventsLogFile),
base::FilePath(touch_evdev_log_filename));
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
log_paths.push_back(base::FilePath(touch_evdev_log_filename));
}
}
// Compress touchpad/mouse logs asynchronously
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&CompressDumpedLog, std::move(log_paths_to_be_compressed)),
base::BindOnce(std::move(reply), log_paths));
}
} // namespace ui