<commit_before>/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2011 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

// mapnik
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/face_set.hpp>
#include <mapnik/util/fs.hpp>

// boost
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread/locks.hpp>

// stl
#include <algorithm>
#include <stdexcept>
#include <sstream>
#include <fstream>
#include <cstdlib>

// freetype2
extern "C"
{
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MODULE_H
}

void* _Alloc_Func(FT_Memory memory, long size)
{
    return std::malloc(size);
}

void _Free_Func(FT_Memory memory, void *block)
{
    std::free(block);
}

void* _Realloc_Func(FT_Memory memory, long cur_size, long new_size, void* block)
{
    return std::realloc(block, new_size);
}

namespace mapnik
{

void init_freetype(FT_Memory memory, FT_Library & library)
{
    memory->alloc = _Alloc_Func;
    memory->free = _Free_Func;
    memory->realloc = _Realloc_Func;
    FT_Error error = FT_New_Library(memory, &library );
    if (error) {
        std::ostringstream runtime_error;
        runtime_error << "Could not load FreeType2 library: FT_Error " << error;
        throw std::runtime_error(runtime_error.str());
    }
    FT_Add_Default_Modules(library);
}

freetype_engine::freetype_engine()
    : library_(nullptr),
      memory_(new FT_MemoryRec_)
{
    init_freetype(&*memory_, library_);
}

freetype_engine::~freetype_engine()
{
    FT_Done_Library(library_);
}

bool freetype_engine::is_font_file(std::string const& file_name)
{
    // only accept files that will be matched by freetype2's `figurefiletype()`
    std::string fn = file_name;
    std::transform(fn.begin(), fn.end(), fn.begin(), ::tolower);
    return boost::algorithm::ends_with(fn,std::string(".ttf")) ||
        boost::algorithm::ends_with(fn,std::string(".otf")) ||
        boost::algorithm::ends_with(fn,std::string(".ttc")) ||
        boost::algorithm::ends_with(fn,std::string(".pfa")) ||
        boost::algorithm::ends_with(fn,std::string(".pfb")) ||
        boost::algorithm::ends_with(fn,std::string(".ttc")) ||
        boost::algorithm::ends_with(fn,std::string(".woff"))||
        // Plus OSX custom ext
        boost::algorithm::ends_with(fn,std::string(".dfont"));
}

bool freetype_engine::register_font(std::string const& file_name)
{
// #ifdef MAPNIK_THREADSAFE
    std::lock_guard<std::mutex> guard(mutex_);
// #endif
    std::unique_ptr<FT_MemoryRec_> memory(new FT_MemoryRec_);
    FT_Library library = 0;
    init_freetype(&*memory, library);
    bool result = register_font_impl(file_name, library);
    FT_Done_Library(library);
    return result;
}

bool freetype_engine::register_font_impl(std::string const& file_name, FT_LibraryRec_ * library)
{
    FT_Face face = 0;
    int num_faces = 0;
    bool success = false;
    // some font files have multiple fonts in a file
    // the count is in the 'root' face library[0]
    // see the FT_FaceRec in freetype.h
    for ( int i = 0; face == 0 || i < num_faces; i++ ) {
        // if face is null then this is the first face
        FT_Error error = FT_New_Face (library,file_name.c_str(),i,&face);
        if (error)
        {
            break;
        }
        // store num_faces locally, after FT_Done_Face it can not be accessed any more
        if (!num_faces)
            num_faces = face->num_faces;
        // some fonts can lack names, skip them
        // http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
        if (face->family_name && face->style_name)
        {
            std::string name = std::string(face->family_name) + " " + std::string(face->style_name);
            // skip fonts with leading . in the name
            if (!boost::algorithm::starts_with(name,"."))
            {
                name2file_.insert(std::make_pair(name, std::make_pair(i,file_name)));
                success = true;
            }
        }
        else
        {
            std::ostringstream s;
            s << "Warning: unable to load font file '" << file_name << "' ";
            if (!face->family_name && !face->style_name)
                s << "which lacks both a family name and style name";
            else if (face->family_name)
                s << "which reports a family name of '" << std::string(face->family_name) << "' and lacks a style name";
            else if (face->style_name)
                s << "which reports a style name of '" << std::string(face->style_name) << "' and lacks a family name";

            std::cerr << "register_font: " << s.str();
        }
    }

    if (face) FT_Done_Face(face);

    return success;
}

bool freetype_engine::register_fonts(std::string const& dir, bool recurse)
{
// #ifdef MAPNIK_THREADSAFE
    std::lock_guard<std::mutex> guard(mutex_);
// #endif
    std::unique_ptr<FT_MemoryRec_> memory(new FT_MemoryRec_);
    FT_Library library = 0;
    init_freetype(&*memory, library);
    bool result = register_fonts_impl(dir, library, recurse);
    FT_Done_Library(library);
    return result;
}

bool freetype_engine::register_fonts_impl(std::string const& dir, FT_LibraryRec_ * library, bool recurse)
{
    if (!mapnik::util::exists(dir))
    {
        return false;
    }
    if (!mapnik::util::is_directory(dir))
    {
        return mapnik::freetype_engine::register_font_impl(dir, library);
    }
    bool success = false;
    try
    {
        boost::filesystem::directory_iterator end_itr;
        for (boost::filesystem::directory_iterator itr(dir); itr != end_itr; ++itr)
        {
#if (BOOST_FILESYSTEM_VERSION == 3)
            std::string file_name = itr->path().string();
#else // v2
            std::string file_name = itr->string();
#endif
            if (boost::filesystem::is_directory(*itr) && recurse)
            {
                if (register_fonts_impl(file_name, library, true))
                {
                    success = true;
                }
            }
            else
            {
#if (BOOST_FILESYSTEM_VERSION == 3)
                std::string base_name = itr->path().filename().string();
#else // v2
                std::string base_name = itr->filename();
#endif
                if (!boost::algorithm::starts_with(base_name,".") &&
                    boost::filesystem::is_regular_file(file_name) &&
                    is_font_file(file_name))
                {
                    if (mapnik::freetype_engine::register_font_impl(file_name, library))
                    {
                        success = true;
                    }
                }
            }
        }
    }
    catch (std::exception const& ex)
    {
        std::cerr << "register_fonts: " << ex.what() << '\n';
    }
    return success;
}

std::vector<std::string> freetype_engine::face_names ()
{
    std::vector<std::string> names;
    for (auto const& kv : name2file_)
    {
        names.push_back(kv.first);
    }
    return names;
}

std::map<std::string,std::pair<int,std::string> > const& freetype_engine::get_mapping()
{
    return name2file_;
}

face_ptr freetype_engine::create_face(std::string const& family_name)
{
    auto itr = name2file_.find(family_name);
    if (itr != name2file_.end())
    {
        std::lock_guard<std::mutex> guard(mutex_);

        FT_Face ft_face;
        glyph_cache_ptr glyphs;

        // Find face shared glyphs cache or create it if it doesn't exist yet.
        auto glyph_cache_map_itr = glyph_cache_map_.find(itr->second.second);

        if (glyph_cache_map_itr != glyph_cache_map_.end()) // glyph cache exists
        {
            glyphs = glyph_cache_map_itr->second;
        }
        else
        {
            std::pair<std::map<const std::string, glyph_cache_ptr>::iterator, bool> glyphs_result
                = glyph_cache_map_.emplace(itr->second.second, std::make_shared<glyph_cache_type>());

            glyphs = glyphs_result.first->second;
        }

        auto mem_font_itr = memory_fonts_.find(itr->second.second);

        if (mem_font_itr != memory_fonts_.end()) // memory font
        {
            FT_Error error = FT_New_Memory_Face(library_,
                                                reinterpret_cast<FT_Byte const*>(mem_font_itr->second.c_str()),
                                                static_cast<FT_Long>(mem_font_itr->second.size()), // size
                                                itr->second.first, // face index
                                                &ft_face);

            if (!error) return std::make_shared<face>(ft_face, glyphs);
        }
        else
        {
            // load font into memory
            std::ifstream is(itr->second.second.c_str() , std::ios::binary);
            std::string buffer((std::istreambuf_iterator<char>(is)),
                               std::istreambuf_iterator<char>());
            auto result = memory_fonts_.insert(std::make_pair(itr->second.second, buffer));

            FT_Error error = FT_New_Memory_Face (library_,
                                                 reinterpret_cast<FT_Byte const*>(result.first->second.c_str()),
                                                 static_cast<FT_Long>(buffer.size()),
                                                 itr->second.first,
                                                 &ft_face);
            if (!error) return std::make_shared<face>(ft_face, glyphs);
            else
            {
                // we can't load font, erase it.
                memory_fonts_.erase(result.first);
                // TODO: erase from glyphs_cache_map_?
            }
        }
    }
    return face_ptr();
}

template <typename T>
face_ptr face_manager<T>::get_face(std::string const& name)
{
    auto itr = face_ptr_cache_.find(name);
    if (itr != face_ptr_cache_.end())
    {
        return itr->second;
    }
    else
    {
        face_ptr face = engine_.create_face(name);
        if (face)
        {
            face_ptr_cache_.insert(make_pair(name,face));
        }
        return face;
    }
}

template <typename T>
face_set_ptr face_manager<T>::get_face_set()
{
    face_set_ptr fset = std::make_shared<face_set>();
    return fset;
}

template <typename T>
face_set_ptr face_manager<T>::get_face_set(std::string const& name)
{
    face_set_ptr fset = std::make_shared<face_set>();
    if (face_ptr face = get_face(name))
    {
        fset->add(face);
    }
#ifdef MAPNIK_LOG
    else
    {
        std::ostringstream runtime_error;
        runtime_error << "Failed to find face " << name;
        throw std::runtime_error(runtime_error.str());
    }
#endif
    return fset;
}

template <typename T>
face_set_ptr face_manager<T>::get_face_set(font_set const& font_set)
{
    std::vector<std::string> const& names = font_set.get_face_names();
    face_set_ptr fset = std::make_shared<face_set>();
    for (auto const& name  : names)
    {
        face_ptr face = get_face(name);
        if (face)
        {
            fset->add(face);
        }
#ifdef MAPNIK_LOG
        else
        {
          std::ostringstream runtime_error;
          runtime_error << "Failed to find face " << *name;
          throw std::runtime_error(runtime_error.str());
        }
#endif
    }
    return fset;
}

// #ifdef MAPNIK_THREADSAFE
std::mutex freetype_engine::mutex_;
// #endif
std::map<std::string,std::pair<int,std::string> > freetype_engine::name2file_;
std::map<std::string,std::string> freetype_engine::memory_fonts_;
std::map<const std::string,glyph_cache_ptr> freetype_engine::glyph_cache_map_;
template class face_manager<freetype_engine>;

}
<commit_msg>assume MAPNIK_LOG<commit_after>/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2011 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

// mapnik
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/face_set.hpp>
#include <mapnik/util/fs.hpp>

// boost
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread/locks.hpp>

// stl
#include <algorithm>
#include <stdexcept>
#include <sstream>
#include <fstream>
#include <cstdlib>

// freetype2
extern "C"
{
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MODULE_H
}

void* _Alloc_Func(FT_Memory memory, long size)
{
    return std::malloc(size);
}

void _Free_Func(FT_Memory memory, void *block)
{
    std::free(block);
}

void* _Realloc_Func(FT_Memory memory, long cur_size, long new_size, void* block)
{
    return std::realloc(block, new_size);
}

namespace mapnik
{

void init_freetype(FT_Memory memory, FT_Library & library)
{
    memory->alloc = _Alloc_Func;
    memory->free = _Free_Func;
    memory->realloc = _Realloc_Func;
    FT_Error error = FT_New_Library(memory, &library );
    if (error) {
        std::ostringstream runtime_error;
        runtime_error << "Could not load FreeType2 library: FT_Error " << error;
        throw std::runtime_error(runtime_error.str());
    }
    FT_Add_Default_Modules(library);
}

freetype_engine::freetype_engine()
    : library_(nullptr),
      memory_(new FT_MemoryRec_)
{
    init_freetype(&*memory_, library_);
}

freetype_engine::~freetype_engine()
{
    FT_Done_Library(library_);
}

bool freetype_engine::is_font_file(std::string const& file_name)
{
    // only accept files that will be matched by freetype2's `figurefiletype()`
    std::string fn = file_name;
    std::transform(fn.begin(), fn.end(), fn.begin(), ::tolower);
    return boost::algorithm::ends_with(fn,std::string(".ttf")) ||
        boost::algorithm::ends_with(fn,std::string(".otf")) ||
        boost::algorithm::ends_with(fn,std::string(".ttc")) ||
        boost::algorithm::ends_with(fn,std::string(".pfa")) ||
        boost::algorithm::ends_with(fn,std::string(".pfb")) ||
        boost::algorithm::ends_with(fn,std::string(".ttc")) ||
        boost::algorithm::ends_with(fn,std::string(".woff"))||
        // Plus OSX custom ext
        boost::algorithm::ends_with(fn,std::string(".dfont"));
}

bool freetype_engine::register_font(std::string const& file_name)
{
// #ifdef MAPNIK_THREADSAFE
    std::lock_guard<std::mutex> guard(mutex_);
// #endif
    std::unique_ptr<FT_MemoryRec_> memory(new FT_MemoryRec_);
    FT_Library library = 0;
    init_freetype(&*memory, library);
    bool result = register_font_impl(file_name, library);
    FT_Done_Library(library);
    return result;
}

bool freetype_engine::register_font_impl(std::string const& file_name, FT_LibraryRec_ * library)
{
    FT_Face face = 0;
    int num_faces = 0;
    bool success = false;
    // some font files have multiple fonts in a file
    // the count is in the 'root' face library[0]
    // see the FT_FaceRec in freetype.h
    for ( int i = 0; face == 0 || i < num_faces; i++ ) {
        // if face is null then this is the first face
        FT_Error error = FT_New_Face (library,file_name.c_str(),i,&face);
        if (error)
        {
            break;
        }
        // store num_faces locally, after FT_Done_Face it can not be accessed any more
        if (!num_faces)
            num_faces = face->num_faces;
        // some fonts can lack names, skip them
        // http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
        if (face->family_name && face->style_name)
        {
            std::string name = std::string(face->family_name) + " " + std::string(face->style_name);
            // skip fonts with leading . in the name
            if (!boost::algorithm::starts_with(name,"."))
            {
                name2file_.insert(std::make_pair(name, std::make_pair(i,file_name)));
                success = true;
            }
        }
        else
        {
            std::ostringstream s;
            s << "Warning: unable to load font file '" << file_name << "' ";
            if (!face->family_name && !face->style_name)
                s << "which lacks both a family name and style name";
            else if (face->family_name)
                s << "which reports a family name of '" << std::string(face->family_name) << "' and lacks a style name";
            else if (face->style_name)
                s << "which reports a style name of '" << std::string(face->style_name) << "' and lacks a family name";

            std::cerr << "register_font: " << s.str();
        }
    }

    if (face) FT_Done_Face(face);

    return success;
}

bool freetype_engine::register_fonts(std::string const& dir, bool recurse)
{
// #ifdef MAPNIK_THREADSAFE
    std::lock_guard<std::mutex> guard(mutex_);
// #endif
    std::unique_ptr<FT_MemoryRec_> memory(new FT_MemoryRec_);
    FT_Library library = 0;
    init_freetype(&*memory, library);
    bool result = register_fonts_impl(dir, library, recurse);
    FT_Done_Library(library);
    return result;
}

bool freetype_engine::register_fonts_impl(std::string const& dir, FT_LibraryRec_ * library, bool recurse)
{
    if (!mapnik::util::exists(dir))
    {
        return false;
    }
    if (!mapnik::util::is_directory(dir))
    {
        return mapnik::freetype_engine::register_font_impl(dir, library);
    }
    bool success = false;
    try
    {
        boost::filesystem::directory_iterator end_itr;
        for (boost::filesystem::directory_iterator itr(dir); itr != end_itr; ++itr)
        {
#if (BOOST_FILESYSTEM_VERSION == 3)
            std::string file_name = itr->path().string();
#else // v2
            std::string file_name = itr->string();
#endif
            if (boost::filesystem::is_directory(*itr) && recurse)
            {
                if (register_fonts_impl(file_name, library, true))
                {
                    success = true;
                }
            }
            else
            {
#if (BOOST_FILESYSTEM_VERSION == 3)
                std::string base_name = itr->path().filename().string();
#else // v2
                std::string base_name = itr->filename();
#endif
                if (!boost::algorithm::starts_with(base_name,".") &&
                    boost::filesystem::is_regular_file(file_name) &&
                    is_font_file(file_name))
                {
                    if (mapnik::freetype_engine::register_font_impl(file_name, library))
                    {
                        success = true;
                    }
                }
            }
        }
    }
    catch (std::exception const& ex)
    {
        std::cerr << "register_fonts: " << ex.what() << '\n';
    }
    return success;
}

std::vector<std::string> freetype_engine::face_names ()
{
    std::vector<std::string> names;
    for (auto const& kv : name2file_)
    {
        names.push_back(kv.first);
    }
    return names;
}

std::map<std::string,std::pair<int,std::string> > const& freetype_engine::get_mapping()
{
    return name2file_;
}

face_ptr freetype_engine::create_face(std::string const& family_name)
{
    auto itr = name2file_.find(family_name);
    if (itr != name2file_.end())
    {
        std::lock_guard<std::mutex> guard(mutex_);

        FT_Face ft_face;
        glyph_cache_ptr glyphs;

        // Find face shared glyphs cache or create it if it doesn't exist yet.
        auto glyph_cache_map_itr = glyph_cache_map_.find(itr->second.second);

        if (glyph_cache_map_itr != glyph_cache_map_.end()) // glyph cache exists
        {
            glyphs = glyph_cache_map_itr->second;
        }
        else
        {
            std::pair<std::map<const std::string, glyph_cache_ptr>::iterator, bool> glyphs_result
                = glyph_cache_map_.emplace(itr->second.second, std::make_shared<glyph_cache_type>());

            glyphs = glyphs_result.first->second;
        }

        auto mem_font_itr = memory_fonts_.find(itr->second.second);

        if (mem_font_itr != memory_fonts_.end()) // memory font
        {
            FT_Error error = FT_New_Memory_Face(library_,
                                                reinterpret_cast<FT_Byte const*>(mem_font_itr->second.c_str()),
                                                static_cast<FT_Long>(mem_font_itr->second.size()), // size
                                                itr->second.first, // face index
                                                &ft_face);

            if (!error) return std::make_shared<face>(ft_face, glyphs);
        }
        else
        {
            // load font into memory
            std::ifstream is(itr->second.second.c_str() , std::ios::binary);
            std::string buffer((std::istreambuf_iterator<char>(is)),
                               std::istreambuf_iterator<char>());
            auto result = memory_fonts_.insert(std::make_pair(itr->second.second, buffer));

            FT_Error error = FT_New_Memory_Face (library_,
                                                 reinterpret_cast<FT_Byte const*>(result.first->second.c_str()),
                                                 static_cast<FT_Long>(buffer.size()),
                                                 itr->second.first,
                                                 &ft_face);
            if (!error) return std::make_shared<face>(ft_face, glyphs);
            else
            {
                // we can't load font, erase it.
                memory_fonts_.erase(result.first);
                // TODO: erase from glyphs_cache_map_?
            }
        }
    }
    return face_ptr();
}

template <typename T>
face_ptr face_manager<T>::get_face(std::string const& name)
{
    auto itr = face_ptr_cache_.find(name);
    if (itr != face_ptr_cache_.end())
    {
        return itr->second;
    }
    else
    {
        face_ptr face = engine_.create_face(name);
        if (face)
        {
            face_ptr_cache_.insert(make_pair(name,face));
        }
        return face;
    }
}

template <typename T>
face_set_ptr face_manager<T>::get_face_set()
{
    face_set_ptr fset = std::make_shared<face_set>();
    return fset;
}

template <typename T>
face_set_ptr face_manager<T>::get_face_set(std::string const& name)
{
    face_set_ptr fset = std::make_shared<face_set>();
    if (face_ptr face = get_face(name))
    {
        fset->add(face);
    }
// #ifdef MAPNIK_LOG
    else
    {
        std::ostringstream runtime_error;
        runtime_error << "Failed to find face " << name;
        throw std::runtime_error(runtime_error.str());
    }
// #endif
    return fset;
}

template <typename T>
face_set_ptr face_manager<T>::get_face_set(font_set const& font_set)
{
    std::vector<std::string> const& names = font_set.get_face_names();
    face_set_ptr fset = std::make_shared<face_set>();
    for (auto const& name  : names)
    {
        face_ptr face = get_face(name);
        if (face)
        {
            fset->add(face);
        }
// #ifdef MAPNIK_LOG
        else
        {
          std::ostringstream runtime_error;
          runtime_error << "Failed to find face " << name;
          throw std::runtime_error(runtime_error.str());
        }
// #endif
    }
    return fset;
}

// #ifdef MAPNIK_THREADSAFE
std::mutex freetype_engine::mutex_;
// #endif
std::map<std::string,std::pair<int,std::string> > freetype_engine::name2file_;
std::map<std::string,std::string> freetype_engine::memory_fonts_;
std::map<const std::string,glyph_cache_ptr> freetype_engine::glyph_cache_map_;
template class face_manager<freetype_engine>;

}
<|endoftext|>


<commit_before><commit_msg>grt: ignore routed nets<commit_after><|endoftext|>


<commit_before>///////////////////////////////////////////////////////////////////////////////
// BSD 3-Clause License
//
// Copyright (c) 2019, The Regents of the University of California
// 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 the copyright holder 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 HOLDER 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.

#include "scriptWidget.h"

#include <mutex>
#include <errno.h>
#include <unistd.h>

#include <QCoreApplication>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QTimer>
#include <QVBoxLayout>

#include "gui/gui.h"
#include "ord/OpenRoad.hh"
#include "spdlog/formatter.h"
#include "spdlog/sinks/base_sink.h"

namespace gui {

ScriptWidget::ScriptWidget(QWidget* parent)
    : QDockWidget("Scripting", parent),
      output_(new QTextEdit(this)),
      input_(new TclCmdInputWidget(this)),
      pauser_(new QPushButton("Idle", this)),
      pause_timer_(std::make_unique<QTimer>()),
      interp_(nullptr),
      history_(),
      history_buffer_last_(),
      historyPosition_(0),
      paused_(false),
      logger_(nullptr),
      buffer_outputs_(false),
      sink_(nullptr)
{
  setObjectName("scripting");  // for settings

  output_->setReadOnly(true);
  pauser_->setEnabled(false);
  pauser_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);

  QHBoxLayout* inner_layout = new QHBoxLayout;
  inner_layout->addWidget(pauser_);
  inner_layout->addWidget(input_);

  QVBoxLayout* layout = new QVBoxLayout;
  layout->addWidget(output_, /* stretch */ 1);
  layout->addLayout(inner_layout);

  QWidget* container = new QWidget(this);
  container->setLayout(layout);

  connect(input_, SIGNAL(completeCommand(const QString&)), this, SLOT(executeCommand(const QString&)));
  connect(this, SIGNAL(commandExecuted(int)), input_, SLOT(commandExecuted(int)));
  connect(input_, SIGNAL(historyGoBack()), this, SLOT(goBackHistory()));
  connect(input_, SIGNAL(historyGoForward()), this, SLOT(goForwardHistory()));
  connect(input_, SIGNAL(textChanged()), this, SLOT(outputChanged()));
  connect(output_, SIGNAL(textChanged()), this, SLOT(outputChanged()));
  connect(pauser_, SIGNAL(pressed()), this, SLOT(pauserClicked()));
  connect(pause_timer_.get(), SIGNAL(timeout()), this, SLOT(unpause()));

  connect(this,
          SIGNAL(addToOutput(const QString&, const QColor&)),
          this,
          SLOT(addTextToOutput(const QString&, const QColor&)),
          Qt::QueuedConnection);

  setWidget(container);
}

ScriptWidget::~ScriptWidget()
{
  if (logger_ != nullptr) {
    // make sure to remove the Gui sink from logger
    logger_->removeSink(sink_);
  }

  // restore old exit
  Tcl_DeleteCommand(interp_, "exit");
  Tcl_Eval(interp_, "rename ::tcl::openroad::exit exit");
}

int ScriptWidget::tclExitHandler(ClientData instance_data,
                                 Tcl_Interp *interp,
                                 int argc,
                                 const char **argv) {
  ScriptWidget* widget = (ScriptWidget*) instance_data;
  // announces exit to Qt
  emit widget->tclExiting();

  return TCL_OK;
}

void ScriptWidget::setupTcl(Tcl_Interp* interp, bool do_init_openroad)
{
  interp_ = interp;

  // Overwrite exit to allow Qt to handle exit
  Tcl_Eval(interp_, "rename exit ::tcl::openroad::exit");
  Tcl_CreateCommand(interp_, "exit", ScriptWidget::tclExitHandler, this, nullptr);

  if (do_init_openroad) {
    // OpenRoad is not initialized
    pauser_->setText("Running");
    pauser_->setStyleSheet("background-color: red");
    int setup_tcl_result = ord::tclAppInit(interp_);
    pauser_->setText("Idle");
    pauser_->setStyleSheet("");

    addTclResultToOutput(setup_tcl_result);
  } else {
    Gui::get()->load_design();
  }

  input_->init(interp_);
}

void ScriptWidget::executeCommand(const QString& command, bool echo)
{
  if (echo) {
    // Show the command that we executed
    addCommandToOutput(command);
  }

  int return_code = executeTclCommand(command);

  // Show its output
  addTclResultToOutput(return_code);

  if (return_code == TCL_OK) {
    if (echo) {
      // record the successful command to tcl history command
      Tcl_RecordAndEval(interp_, command.toLatin1().data(), TCL_NO_EVAL);
    }

    // Update history; ignore repeated commands and keep last 100
    const int history_limit = 100;
    if (history_.empty() || command != history_.last()) {
      if (history_.size() == history_limit) {
        history_.pop_front();
      }

      history_.append(command);
    }
    historyPosition_ = history_.size();
  }

  emit commandExecuted(return_code);
}

void ScriptWidget::executeSilentCommand(const QString& command)
{
  int return_code = executeTclCommand(command);

  if (return_code != TCL_OK) {
    // Show its error output
    addTclResultToOutput(return_code);
  }

  emit commandExecuted(return_code);
}

int ScriptWidget::executeTclCommand(const QString& command)
{
  pauser_->setText("Running");
  pauser_->setStyleSheet("background-color: red");

  int return_code = Tcl_Eval(interp_, command.toLatin1().data());

  pauser_->setText("Idle");
  pauser_->setStyleSheet("");

  return return_code;
}

void ScriptWidget::addCommandToOutput(const QString& cmd)
{
  const QString first_line_prefix    = ">>> ";
  const QString continue_line_prefix = "... ";

  QString command = first_line_prefix + cmd;
  command.replace("\n", "\n" + continue_line_prefix);

  addToOutput(command, cmd_msg_);
}

void ScriptWidget::addTclResultToOutput(int return_code)
{
  // Show the return value color-coded by ok/err.
  const char* result = Tcl_GetString(Tcl_GetObjResult(interp_));
  if (result[0] != '\0') {
    if (return_code == TCL_OK) {
      addToOutput(result, tcl_ok_msg_);
    } else {
      try {
        logger_->error(utl::GUI, 70, result);
      } catch (const std::runtime_error& /* e */) {
        // do nothing
      }
    }
  }
}

void ScriptWidget::addLogToOutput(const QString& text, const QColor& color)
{
  addToOutput(text, color);
}

void ScriptWidget::addReportToOutput(const QString& text)
{
  addToOutput(text, buffer_msg_);
}

void ScriptWidget::addTextToOutput(const QString& text, const QColor& color)
{
  // make sure cursor is at the end of the document
  output_->moveCursor(QTextCursor::End);

  QString output_text = text;
  if (text.endsWith('\n')) {
    // remove last new line
    output_text.chop(1);
  }

  // set new text color
  output_->setTextColor(color);

  QStringList output;
  for (QString& text_line : output_text.split('\n')) {
    // check for line length limits
    if (text_line.size() > max_output_line_length_) {
      text_line = text_line.left(max_output_line_length_-3);
      text_line += "...";
    }

    output.append(text_line);
  }
  // output new text
  output_->append(output.join("\n"));
}

void ScriptWidget::goForwardHistory()
{
  if (historyPosition_ < history_.size() - 1) {
    ++historyPosition_;
    input_->setText(history_[historyPosition_]);
  } else if (historyPosition_ == history_.size() - 1) {
    ++historyPosition_;
    input_->setText(history_buffer_last_);
  }
}

void ScriptWidget::goBackHistory()
{
  if (historyPosition_ > 0) {
    if (historyPosition_ == history_.size()) {
      // whats in the buffer is the last thing the user was editing
      history_buffer_last_ = input_->text();
    }
    --historyPosition_;
    input_->setText(history_[historyPosition_]);
  }
}

void ScriptWidget::readSettings(QSettings* settings)
{
  settings->beginGroup("scripting");
  history_ = settings->value("history").toStringList();
  historyPosition_ = history_.size();

  input_->readSettings(settings);

  settings->endGroup();
}

void ScriptWidget::writeSettings(QSettings* settings)
{
  settings->beginGroup("scripting");
  settings->setValue("history", history_);

  input_->writeSettings(settings);

  settings->endGroup();
}

void ScriptWidget::pause(int timeout)
{
  QString prior_text = pauser_->text();
  bool prior_enable = pauser_->isEnabled();
  QString prior_style = pauser_->styleSheet();
  pauser_->setText("Continue");
  pauser_->setStyleSheet("background-color: yellow");
  pauser_->setEnabled(true);
  paused_ = true;

  input_->setReadOnly(true);

  triggerPauseCountDown(timeout);

  // Keep processing events until the user continues
  while (paused_) {
    QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
  }

  pauser_->setText(prior_text);
  pauser_->setStyleSheet(prior_style);
  pauser_->setEnabled(prior_enable);

  input_->setReadOnly(false);

  // Make changes visible while command runs
  QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}

void ScriptWidget::unpause()
{
  paused_ = false;
}

void ScriptWidget::triggerPauseCountDown(int timeout)
{
  if (timeout == 0) {
    return;
  }

  pause_timer_->setInterval(timeout);
  pause_timer_->start();
  QTimer::singleShot(timeout, this, SLOT(updatePauseTimeout()));
  updatePauseTimeout();
}

void ScriptWidget::updatePauseTimeout()
{
  if (!paused_) {
    // already unpaused
    return;
  }

  const int one_second = 1000;

  int seconds = pause_timer_->remainingTime() / one_second;
  pauser_->setText("Continue (" + QString::number(seconds) + "s)");

  QTimer::singleShot(one_second, this, SLOT(updatePauseTimeout()));
}

void ScriptWidget::pauserClicked()
{
  pause_timer_->stop();
  paused_ = false;
}

void ScriptWidget::outputChanged()
{
  // ensure the new output is visible
  output_->ensureCursorVisible();
  // Make changes visible
  if (!buffer_outputs_) {
    QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
  }
}

void ScriptWidget::bufferOutputs(bool state)
{
  buffer_outputs_ = state;
}

void ScriptWidget::resizeEvent(QResizeEvent* event)
{
  input_->setMaximumHeight(event->size().height() - output_->sizeHint().height());
  QDockWidget::resizeEvent(event);
}

void ScriptWidget::setFont(const QFont& font)
{
  QDockWidget::setFont(font);
  output_->setFont(font);
  input_->setFont(font);
}

// This class is an spdlog sink that writes the messages into the output
// area.
template <typename Mutex>
class ScriptWidget::GuiSink : public spdlog::sinks::base_sink<Mutex>
{
 public:
  GuiSink(ScriptWidget* widget) : widget_(widget) {}

 protected:
  void sink_it_(const spdlog::details::log_msg& msg) override
  {
    // Convert the msg into a formatted string
    spdlog::memory_buf_t formatted;

    mutex_.lock(); // formatter checks and caches some information
    this->formatter_->format(msg, formatted);
    mutex_.unlock();
    const QString formatted_msg = QString::fromStdString(std::string(formatted.data(), formatted.size()));

    if (msg.level == spdlog::level::level_enum::off) {
      // this comes from a ->report
      widget_->addReportToOutput(formatted_msg);
    }
    else {
      // select error message color if message level is error or above.
      const QColor& msg_color = msg.level >= spdlog::level::level_enum::err ? widget_->tcl_error_msg_ : widget_->buffer_msg_;

      widget_->addLogToOutput(formatted_msg, msg_color);
    }

    // process widget event queue, if main thread will process new text, otherwise there is nothing to process from this thread.
    QCoreApplication::sendPostedEvents(widget_);
  }

  void flush_() override {}

 private:
  ScriptWidget* widget_;
  std::mutex mutex_;
};

void ScriptWidget::setLogger(utl::Logger* logger)
{
  // use spdlog::details::null_mutex instead of std::mutex, Qt will handle the thread transfers to the output viewer
  // null_mutex prevents deadlock (by not locking) when the logging causes a redraw, which then causes another logging event.
  sink_ = std::make_shared<GuiSink<spdlog::details::null_mutex>>(this);
  logger_ = logger;
  logger->addSink(sink_);
}

}  // namespace gui
<commit_msg>gui: adding missing check for current thread in sink_it_<commit_after>///////////////////////////////////////////////////////////////////////////////
// BSD 3-Clause License
//
// Copyright (c) 2019, The Regents of the University of California
// 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 the copyright holder 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 HOLDER 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.

#include "scriptWidget.h"

#include <mutex>
#include <errno.h>
#include <unistd.h>

#include <QCoreApplication>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QThread>
#include <QTimer>
#include <QVBoxLayout>

#include "gui/gui.h"
#include "ord/OpenRoad.hh"
#include "spdlog/formatter.h"
#include "spdlog/sinks/base_sink.h"

namespace gui {

ScriptWidget::ScriptWidget(QWidget* parent)
    : QDockWidget("Scripting", parent),
      output_(new QTextEdit(this)),
      input_(new TclCmdInputWidget(this)),
      pauser_(new QPushButton("Idle", this)),
      pause_timer_(std::make_unique<QTimer>()),
      interp_(nullptr),
      history_(),
      history_buffer_last_(),
      historyPosition_(0),
      paused_(false),
      logger_(nullptr),
      buffer_outputs_(false),
      sink_(nullptr)
{
  setObjectName("scripting");  // for settings

  output_->setReadOnly(true);
  pauser_->setEnabled(false);
  pauser_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);

  QHBoxLayout* inner_layout = new QHBoxLayout;
  inner_layout->addWidget(pauser_);
  inner_layout->addWidget(input_);

  QVBoxLayout* layout = new QVBoxLayout;
  layout->addWidget(output_, /* stretch */ 1);
  layout->addLayout(inner_layout);

  QWidget* container = new QWidget(this);
  container->setLayout(layout);

  connect(input_, SIGNAL(completeCommand(const QString&)), this, SLOT(executeCommand(const QString&)));
  connect(this, SIGNAL(commandExecuted(int)), input_, SLOT(commandExecuted(int)));
  connect(input_, SIGNAL(historyGoBack()), this, SLOT(goBackHistory()));
  connect(input_, SIGNAL(historyGoForward()), this, SLOT(goForwardHistory()));
  connect(input_, SIGNAL(textChanged()), this, SLOT(outputChanged()));
  connect(output_, SIGNAL(textChanged()), this, SLOT(outputChanged()));
  connect(pauser_, SIGNAL(pressed()), this, SLOT(pauserClicked()));
  connect(pause_timer_.get(), SIGNAL(timeout()), this, SLOT(unpause()));

  connect(this,
          SIGNAL(addToOutput(const QString&, const QColor&)),
          this,
          SLOT(addTextToOutput(const QString&, const QColor&)),
          Qt::QueuedConnection);

  setWidget(container);
}

ScriptWidget::~ScriptWidget()
{
  if (logger_ != nullptr) {
    // make sure to remove the Gui sink from logger
    logger_->removeSink(sink_);
  }

  // restore old exit
  Tcl_DeleteCommand(interp_, "exit");
  Tcl_Eval(interp_, "rename ::tcl::openroad::exit exit");
}

int ScriptWidget::tclExitHandler(ClientData instance_data,
                                 Tcl_Interp *interp,
                                 int argc,
                                 const char **argv) {
  ScriptWidget* widget = (ScriptWidget*) instance_data;
  // announces exit to Qt
  emit widget->tclExiting();

  return TCL_OK;
}

void ScriptWidget::setupTcl(Tcl_Interp* interp, bool do_init_openroad)
{
  interp_ = interp;

  // Overwrite exit to allow Qt to handle exit
  Tcl_Eval(interp_, "rename exit ::tcl::openroad::exit");
  Tcl_CreateCommand(interp_, "exit", ScriptWidget::tclExitHandler, this, nullptr);

  if (do_init_openroad) {
    // OpenRoad is not initialized
    pauser_->setText("Running");
    pauser_->setStyleSheet("background-color: red");
    int setup_tcl_result = ord::tclAppInit(interp_);
    pauser_->setText("Idle");
    pauser_->setStyleSheet("");

    addTclResultToOutput(setup_tcl_result);
  } else {
    Gui::get()->load_design();
  }

  input_->init(interp_);
}

void ScriptWidget::executeCommand(const QString& command, bool echo)
{
  if (echo) {
    // Show the command that we executed
    addCommandToOutput(command);
  }

  int return_code = executeTclCommand(command);

  // Show its output
  addTclResultToOutput(return_code);

  if (return_code == TCL_OK) {
    if (echo) {
      // record the successful command to tcl history command
      Tcl_RecordAndEval(interp_, command.toLatin1().data(), TCL_NO_EVAL);
    }

    // Update history; ignore repeated commands and keep last 100
    const int history_limit = 100;
    if (history_.empty() || command != history_.last()) {
      if (history_.size() == history_limit) {
        history_.pop_front();
      }

      history_.append(command);
    }
    historyPosition_ = history_.size();
  }

  emit commandExecuted(return_code);
}

void ScriptWidget::executeSilentCommand(const QString& command)
{
  int return_code = executeTclCommand(command);

  if (return_code != TCL_OK) {
    // Show its error output
    addTclResultToOutput(return_code);
  }

  emit commandExecuted(return_code);
}

int ScriptWidget::executeTclCommand(const QString& command)
{
  pauser_->setText("Running");
  pauser_->setStyleSheet("background-color: red");

  int return_code = Tcl_Eval(interp_, command.toLatin1().data());

  pauser_->setText("Idle");
  pauser_->setStyleSheet("");

  return return_code;
}

void ScriptWidget::addCommandToOutput(const QString& cmd)
{
  const QString first_line_prefix    = ">>> ";
  const QString continue_line_prefix = "... ";

  QString command = first_line_prefix + cmd;
  command.replace("\n", "\n" + continue_line_prefix);

  addToOutput(command, cmd_msg_);
}

void ScriptWidget::addTclResultToOutput(int return_code)
{
  // Show the return value color-coded by ok/err.
  const char* result = Tcl_GetString(Tcl_GetObjResult(interp_));
  if (result[0] != '\0') {
    if (return_code == TCL_OK) {
      addToOutput(result, tcl_ok_msg_);
    } else {
      try {
        logger_->error(utl::GUI, 70, result);
      } catch (const std::runtime_error& /* e */) {
        // do nothing
      }
    }
  }
}

void ScriptWidget::addLogToOutput(const QString& text, const QColor& color)
{
  addToOutput(text, color);
}

void ScriptWidget::addReportToOutput(const QString& text)
{
  addToOutput(text, buffer_msg_);
}

void ScriptWidget::addTextToOutput(const QString& text, const QColor& color)
{
  // make sure cursor is at the end of the document
  output_->moveCursor(QTextCursor::End);

  QString output_text = text;
  if (text.endsWith('\n')) {
    // remove last new line
    output_text.chop(1);
  }

  // set new text color
  output_->setTextColor(color);

  QStringList output;
  for (QString& text_line : output_text.split('\n')) {
    // check for line length limits
    if (text_line.size() > max_output_line_length_) {
      text_line = text_line.left(max_output_line_length_-3);
      text_line += "...";
    }

    output.append(text_line);
  }
  // output new text
  output_->append(output.join("\n"));
}

void ScriptWidget::goForwardHistory()
{
  if (historyPosition_ < history_.size() - 1) {
    ++historyPosition_;
    input_->setText(history_[historyPosition_]);
  } else if (historyPosition_ == history_.size() - 1) {
    ++historyPosition_;
    input_->setText(history_buffer_last_);
  }
}

void ScriptWidget::goBackHistory()
{
  if (historyPosition_ > 0) {
    if (historyPosition_ == history_.size()) {
      // whats in the buffer is the last thing the user was editing
      history_buffer_last_ = input_->text();
    }
    --historyPosition_;
    input_->setText(history_[historyPosition_]);
  }
}

void ScriptWidget::readSettings(QSettings* settings)
{
  settings->beginGroup("scripting");
  history_ = settings->value("history").toStringList();
  historyPosition_ = history_.size();

  input_->readSettings(settings);

  settings->endGroup();
}

void ScriptWidget::writeSettings(QSettings* settings)
{
  settings->beginGroup("scripting");
  settings->setValue("history", history_);

  input_->writeSettings(settings);

  settings->endGroup();
}

void ScriptWidget::pause(int timeout)
{
  QString prior_text = pauser_->text();
  bool prior_enable = pauser_->isEnabled();
  QString prior_style = pauser_->styleSheet();
  pauser_->setText("Continue");
  pauser_->setStyleSheet("background-color: yellow");
  pauser_->setEnabled(true);
  paused_ = true;

  input_->setReadOnly(true);

  triggerPauseCountDown(timeout);

  // Keep processing events until the user continues
  while (paused_) {
    QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
  }

  pauser_->setText(prior_text);
  pauser_->setStyleSheet(prior_style);
  pauser_->setEnabled(prior_enable);

  input_->setReadOnly(false);

  // Make changes visible while command runs
  QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}

void ScriptWidget::unpause()
{
  paused_ = false;
}

void ScriptWidget::triggerPauseCountDown(int timeout)
{
  if (timeout == 0) {
    return;
  }

  pause_timer_->setInterval(timeout);
  pause_timer_->start();
  QTimer::singleShot(timeout, this, SLOT(updatePauseTimeout()));
  updatePauseTimeout();
}

void ScriptWidget::updatePauseTimeout()
{
  if (!paused_) {
    // already unpaused
    return;
  }

  const int one_second = 1000;

  int seconds = pause_timer_->remainingTime() / one_second;
  pauser_->setText("Continue (" + QString::number(seconds) + "s)");

  QTimer::singleShot(one_second, this, SLOT(updatePauseTimeout()));
}

void ScriptWidget::pauserClicked()
{
  pause_timer_->stop();
  paused_ = false;
}

void ScriptWidget::outputChanged()
{
  // ensure the new output is visible
  output_->ensureCursorVisible();
  // Make changes visible
  if (!buffer_outputs_) {
    QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
  }
}

void ScriptWidget::bufferOutputs(bool state)
{
  buffer_outputs_ = state;
}

void ScriptWidget::resizeEvent(QResizeEvent* event)
{
  input_->setMaximumHeight(event->size().height() - output_->sizeHint().height());
  QDockWidget::resizeEvent(event);
}

void ScriptWidget::setFont(const QFont& font)
{
  QDockWidget::setFont(font);
  output_->setFont(font);
  input_->setFont(font);
}

// This class is an spdlog sink that writes the messages into the output
// area.
template <typename Mutex>
class ScriptWidget::GuiSink : public spdlog::sinks::base_sink<Mutex>
{
 public:
  GuiSink(ScriptWidget* widget) : widget_(widget) {}

 protected:
  void sink_it_(const spdlog::details::log_msg& msg) override
  {
    // Convert the msg into a formatted string
    spdlog::memory_buf_t formatted;

    mutex_.lock(); // formatter checks and caches some information
    this->formatter_->format(msg, formatted);
    mutex_.unlock();
    const QString formatted_msg = QString::fromStdString(std::string(formatted.data(), formatted.size()));

    if (msg.level == spdlog::level::level_enum::off) {
      // this comes from a ->report
      widget_->addReportToOutput(formatted_msg);
    }
    else {
      // select error message color if message level is error or above.
      const QColor& msg_color = msg.level >= spdlog::level::level_enum::err ? widget_->tcl_error_msg_ : widget_->buffer_msg_;

      widget_->addLogToOutput(formatted_msg, msg_color);
    }

    // process widget event queue, if main thread will process new text, otherwise there is nothing to process from this thread.
    if (QThread::currentThread() == widget_->thread()) {
      QCoreApplication::sendPostedEvents(widget_);
    }
  }

  void flush_() override {}

 private:
  ScriptWidget* widget_;
  std::mutex mutex_;
};

void ScriptWidget::setLogger(utl::Logger* logger)
{
  // use spdlog::details::null_mutex instead of std::mutex, Qt will handle the thread transfers to the output viewer
  // null_mutex prevents deadlock (by not locking) when the logging causes a redraw, which then causes another logging event.
  sink_ = std::make_shared<GuiSink<spdlog::details::null_mutex>>(this);
  logger_ = logger;
  logger->addSink(sink_);
}

}  // namespace gui
<|endoftext|>


<commit_before>// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2015 Intel Corporation. All Rights Reserved.

#if (_MSC_FULL_VER < 180031101)
#error At least Visual Studio 2013 Update 4 is required to compile this backend
#endif

#ifndef NOMINMAX
#define NOMINMAX
#endif

#include "../types.h"
#include "mf-hid.h"
#include "win/win-helpers.h"
#include "metadata.h"

#include <PortableDeviceTypes.h>
//#include <PortableDeviceClassExtension.h>
#include <PortableDevice.h>
#include <Windows.h>
#include <Sensorsapi.h>
#include <sensors.h>
#include <SensAPI.h>
#include <initguid.h>
#include <propkeydef.h>
#include <comutil.h>

#pragma comment(lib, "Sensorsapi.lib")
#pragma comment(lib, "PortableDeviceGuids.lib")

// Windows Filetime is represented in 64 - bit number of 100 - nanosecond intervals since midnight Jan 1, 1601
// To convert to the Unix epoch, subtract 116444736000000000LL to reach Jan 1, 1970.
constexpr uint64_t WIN_FILETIME_2_UNIX_SYSTIME = 116444736000000000LL;

namespace librealsense
{
    namespace platform
    {
        class sensor_events : public ISensorEvents
        {
        public:
            virtual ~sensor_events() = default;

            explicit sensor_events(hid_callback callback) : m_cRef(0), _callback(callback) {}

            STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
            {
                if (ppv == NULL)
                {
                    return E_POINTER;
                }
                if (iid == __uuidof(IUnknown))
                {
                    *ppv = static_cast<IUnknown*>(this);
                }
                else if (iid == __uuidof(ISensorEvents))
                {
                    *ppv = static_cast<ISensorEvents*>(this);
                }
                else
                {
                    *ppv = NULL;
                    return E_NOINTERFACE;
                }
                AddRef();
                return S_OK;
            }

            STDMETHODIMP_(ULONG) AddRef()
            {
                return InterlockedIncrement(&m_cRef);
            }

            STDMETHODIMP_(ULONG) Release()
            {
                ULONG count = InterlockedDecrement(&m_cRef);
                if (count == 0)
                {
                    delete this;
                    return 0;
                }
                return count;
            }

            //
            // ISensorEvents methods.
            //

            STDMETHODIMP OnEvent(
                ISensor *pSensor,
                REFGUID eventID,
                IPortableDeviceValues *pEventData)
            {
                HRESULT hr = S_OK;

                // Handle custom events here.

                return hr;
            }

            STDMETHODIMP OnDataUpdated(ISensor *pSensor, ISensorDataReport *report)
            {
                if (NULL == report ||
                    NULL == pSensor)
                {
                    return E_INVALIDARG;
                }

                BSTR        fName{};
                SYSTEMTIME  sys_time;
                FILETIME    file_time;
                report->GetTimestamp(&sys_time);

                PROPVARIANT var = {};
                // Custom timestamp low
                auto hr = (report->GetSensorValue(SENSOR_DATA_TYPE_CUSTOM_VALUE1, &var));
                if (FAILED(hr)) return S_OK;
                auto customTimestampLow = var.ulVal;

                // Custom timestamp high
                CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_CUSTOM_VALUE2, &var));
                auto customTimestampHigh = var.ulVal;

                // Parse additional custom fields
                CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_CUSTOM_VALUE6, &var));
                uint8_t imu_count = var.bVal;
                CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_CUSTOM_VALUE7, &var));
                uint8_t usb_count = var.bVal;

                /* Retrieve sensor type - Sensor types are more specific groupings than sensor categories. Sensor type IDs are GUIDs that are defined in Sensors.h */

                SENSOR_TYPE_ID type{};

                CHECK_HR(pSensor->GetType(&type));

                double rawX, rawY, rawZ;

                if (type == SENSOR_TYPE_ACCELEROMETER_3D)
                {
                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_X_G, &var));
                    rawX = var.dblVal;

                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_Y_G, &var));
                    rawY = var.dblVal;

                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_Z_G, &var));
                    rawZ = var.dblVal;

                    static constexpr double accelerator_transform_factor = 1000.0;

                    rawX *= accelerator_transform_factor;
                    rawY *= accelerator_transform_factor;
                    rawZ *= accelerator_transform_factor;
                }
                else if (type == SENSOR_TYPE_GYROMETER_3D)
                {
                    // Raw X
                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ANGULAR_VELOCITY_X_DEGREES_PER_SECOND, &var));
                    rawX = var.dblVal;

                    // Raw Y
                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Y_DEGREES_PER_SECOND, &var));
                    rawY = var.dblVal;

                    // Raw Z
                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Z_DEGREES_PER_SECOND, &var));
                    rawZ = var.dblVal;

                    static constexpr double gyro_transform_factor = 10.0;

                    rawX *= gyro_transform_factor;
                    rawY *= gyro_transform_factor;
                    rawZ *= gyro_transform_factor;
                }
                else
                {
                    /* Unsupported sensor */
                    return S_FALSE;
                }

                PropVariantClear(&var);

                sensor_data d{};
                hid_sensor_data data{};
                // Populate HID IMU data - Header
                metadata_hid_raw meta_data{};
                meta_data.header.report_type = md_hid_report_type::hid_report_imu;
                meta_data.header.length = hid_header_size + metadata_imu_report_size;
                meta_data.header.timestamp = customTimestampLow | (customTimestampHigh < 32);
                // Payload:
                meta_data.report_type.imu_report.header.md_type_id = md_type::META_DATA_HID_IMU_REPORT_ID;
                meta_data.report_type.imu_report.header.md_size = metadata_imu_report_size;
                meta_data.report_type.imu_report.flags = static_cast<uint8_t>( md_hid_imu_attributes::custom_timestamp_attirbute |
                                                                                md_hid_imu_attributes::imu_counter_attribute |
                                                                                md_hid_imu_attributes::usb_counter_attribute);
                meta_data.report_type.imu_report.custom_timestamp = customTimestampLow | (customTimestampHigh < 32);
                meta_data.report_type.imu_report.imu_counter = imu_count;
                meta_data.report_type.imu_report.usb_counter = usb_count;

                data.x = static_cast<int16_t>(rawX);
                data.y = static_cast<int16_t>(rawY);
                data.z = static_cast<int16_t>(rawZ);
                data.ts_low = customTimestampLow;
                data.ts_high = customTimestampHigh;

                pSensor->GetFriendlyName(&fName);
                d.sensor.name = CW2A(fName);

                d.fo.pixels = &data;
                d.fo.metadata = &meta_data;
                d.fo.metadata_size = metadata_hid_raw_size;
                d.fo.frame_size = sizeof(data);
                d.fo.backend_time = 0;
                if (SystemTimeToFileTime(&sys_time, &file_time))
                {
                    auto ll_now = (LONGLONG)file_time.dwLowDateTime + ((LONGLONG)(file_time.dwHighDateTime) << 32LL) - WIN_FILETIME_2_UNIX_SYSTIME;
                    d.fo.backend_time = ll_now * 0.0001; //100 nano-sec to millisec
                }

                _callback(d);

                return S_OK;
            }

            STDMETHODIMP OnLeave(
                REFSENSOR_ID sensorID)
            {
                HRESULT hr = S_OK;

                // Perform any housekeeping tasks for the sensor that is leaving.
                // For example, if you have maintained a reference to the sensor,
                // release it now and set the pointer to NULL.

                return hr;
            }

            STDMETHODIMP OnStateChanged(
                ISensor* pSensor,
                SensorState state)
            {
                HRESULT hr = S_OK;

                if (NULL == pSensor)
                {
                    return E_INVALIDARG;
                }

                if (state == SENSOR_STATE_READY)
                {
                    wprintf_s(L"\nTime sensor is now ready.");
                }
                else if (state == SENSOR_STATE_ACCESS_DENIED)
                {
                    wprintf_s(L"\nNo permission for the time sensor.\n");
                    wprintf_s(L"Enable the sensor in the control panel.\n");
                }

                return hr;
            }

        private:
            long m_cRef;
            hid_callback _callback;
        };

        void wmf_hid_device::open(const std::vector<hid_profile>&iio_profiles)
        {
            try
            {
                for (auto& profile_to_open : iio_profiles)
                {
                    for (auto& connected_sensor : _connected_sensors)
                    {
                        if (profile_to_open.sensor_name == connected_sensor->get_sensor_name())
                        {
                            /* Set SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL sensor property to profile */
                            HRESULT hr = S_OK;
                            IPortableDeviceValues* pPropsToSet = NULL; // Input
                            IPortableDeviceValues* pPropsReturn = NULL; // Output

                            /* Create the input object */
                            CHECK_HR(CoCreateInstance(__uuidof(PortableDeviceValues), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pPropsToSet)));

                            /* Add the current report interval property */
                            hr = pPropsToSet->SetUnsignedIntegerValue(SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL, profile_to_open.frequency);
                            if (SUCCEEDED(hr))
                            {
                                // Setting a single property
                                hr = connected_sensor->get_sensor()->SetProperties(pPropsToSet, &pPropsReturn);
                                if (SUCCEEDED(hr))
                                {
                                    _opened_sensors.push_back(connected_sensor);
                                    pPropsReturn->Release();
                                }
                            }

                            pPropsToSet->Release();
                        }
                    }
                }
            }
            catch (...)
            {
                for (auto& connected_sensor : _connected_sensors)
                {
                    connected_sensor.reset();
                }
                _connected_sensors.clear();
                LOG_ERROR("Hid device is busy!");
                throw;
            }
        }

        void wmf_hid_device::close()
        {
            for (auto& open_sensor : _opened_sensors)
            {
                open_sensor.reset();
            }
            _opened_sensors.clear();
        }

        void wmf_hid_device::start_capture(hid_callback callback)
        {
            // Hack, start default profile
            _cb = new sensor_events(callback);
            ISensorEvents* sensorEvents = nullptr;
            CHECK_HR(_cb->QueryInterface(IID_PPV_ARGS(&sensorEvents)));

            for (auto& sensor : _opened_sensors)
            {
                CHECK_HR(sensor->start_capture(sensorEvents));
            }
        }

        void wmf_hid_device::stop_capture()
        {
            for (auto& sensor : _opened_sensors)
            {
                sensor->stop_capture();
            }
            _cb = nullptr;
        }

        std::vector<hid_sensor> wmf_hid_device::get_sensors()
        {
            std::vector<hid_sensor> sensors;

            HRESULT res = S_OK;
            BSTR fName{};

            for (auto& sensor : _opened_sensors)
            {
                sensors.push_back({ sensor->get_sensor_name() });
            }

            SysFreeString(fName);

            return sensors;
        }

        std::vector<uint8_t> wmf_hid_device::get_custom_report_data(const std::string & custom_sensor_name, const std::string & report_name, custom_sensor_report_field report_field)
        {
            return std::vector<uint8_t>();
        }

        void wmf_hid_device::foreach_hid_device(std::function<void(hid_device_info, CComPtr<ISensor>)> action)
        {
            /* Enumerate all HID devices and run action function on each device */
            try
            {
                CComPtr<ISensorManager> pSensorManager = nullptr;
                CComPtr<ISensorCollection> pSensorCollection = nullptr;
                CComPtr<ISensor> pSensor = nullptr;
                ULONG sensorCount = 0;
                HRESULT res{};

                CHECK_HR(CoCreateInstance(CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSensorManager)));

                /* Retrieves a collection containing all sensors associated with category SENSOR_CATEGORY_ALL */
                LOG_HR(res=pSensorManager->GetSensorsByCategory(SENSOR_CATEGORY_ALL, &pSensorCollection));
                if (SUCCEEDED(res))
                {
                    /* Retrieves the count of sensors in the collection */
                    CHECK_HR(pSensorCollection->GetCount(&sensorCount));

                    for (ULONG i = 0; i < sensorCount; i++)
                    {
                        /* Retrieves the sensor at the specified index in the collection */
                        if (SUCCEEDED(pSensorCollection->GetAt(i, &pSensor.p)))
                        {
                            /* Retrieve SENSOR_PROPERTY_FRIENDLY_NAME which is the sensor name that is intended to be seen by the user */
                            BSTR fName{};
                            LOG_HR(res = pSensor->GetFriendlyName(&fName));
                            if (FAILED(res)) fName= L"Unidentified HID sensor";

                            /* Retrieve SENSOR_PROPERTY_PERSISTENT_UNIQUE_ID which is a GUID that uniquely identifies the sensor on the current computer */
                            SENSOR_ID id{};
                            CHECK_HR(pSensor->GetID(&id));

                            /* Retrieve sensor type - Sensor types are more specific groupings than sensor categories. Sensor type IDs are GUIDs that are defined in Sensors.h */
                            SENSOR_TYPE_ID type{};
                            CHECK_HR(pSensor->GetType(&type));

                            CComPtr<IPortableDeviceValues> pValues = nullptr;  // Output
                            hid_device_info info{};

                            /* Retrieves multiple sensor properties */
                            auto hr = pSensor->GetProperties(nullptr, &pValues);
                            if (SUCCEEDED(hr))
                            {
                                /* Get the number of property returned */
                                DWORD propertyCount = 0;
                                hr = pValues->GetCount(&propertyCount);
                                if (SUCCEEDED(hr))
                                {
                                    PROPERTYKEY propertyKey;
                                    PROPVARIANT propertyValue = {};

                                    /* Loop through the properties */
                                    for (DWORD properyIndex = 0; properyIndex < propertyCount; properyIndex++)
                                    {
                                        // Get the value at the current index.
                                        hr = pValues->GetAt(properyIndex, &propertyKey, &propertyValue);
                                        if (SUCCEEDED(hr))
                                        {
                                            if (IsEqualPropertyKey(propertyKey, SENSOR_PROPERTY_DEVICE_PATH))
                                            {
                                                info.device_path = std::string(propertyValue.pwszVal, propertyValue.pwszVal + wcslen(propertyValue.pwszVal));
                                                info.id = std::string(fName, fName + wcslen(fName));

                                                uint16_t vid, pid, mi;
                                                std::string uid, guid;
                                                if (parse_usb_path_multiple_interface(vid, pid, mi, uid, info.device_path, guid))
                                                {
                                                    info.unique_id = "*";
                                                    info.pid = to_string() << std::hex << pid;
                                                    info.vid = to_string() << std::hex << vid;
                                                }
                                            }
                                            if (IsEqualPropertyKey(propertyKey, SENSOR_PROPERTY_SERIAL_NUMBER))
                                            {
                                                info.serial_number = std::string(propertyValue.pwszVal, propertyValue.pwszVal + wcslen(propertyValue.pwszVal));
                                            }
                                        }

                                        PropVariantClear(&propertyValue);
                                    }
                                }
                            }

                            action(info, pSensor);

                            SysFreeString(fName);
                        }
                    }
                }
            }
            catch (...)
            {
                LOG_INFO("Could not enumerate HID devices!");
            }
        }
    }
}
<commit_msg>Save serial_number on sensor info in lower case.<commit_after>// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2015 Intel Corporation. All Rights Reserved.

#if (_MSC_FULL_VER < 180031101)
#error At least Visual Studio 2013 Update 4 is required to compile this backend
#endif

#ifndef NOMINMAX
#define NOMINMAX
#endif

#include "../types.h"
#include "mf-hid.h"
#include "win/win-helpers.h"
#include "metadata.h"

#include <PortableDeviceTypes.h>
//#include <PortableDeviceClassExtension.h>
#include <PortableDevice.h>
#include <Windows.h>
#include <Sensorsapi.h>
#include <sensors.h>
#include <SensAPI.h>
#include <initguid.h>
#include <propkeydef.h>
#include <comutil.h>
#include <string>

#pragma comment(lib, "Sensorsapi.lib")
#pragma comment(lib, "PortableDeviceGuids.lib")

// Windows Filetime is represented in 64 - bit number of 100 - nanosecond intervals since midnight Jan 1, 1601
// To convert to the Unix epoch, subtract 116444736000000000LL to reach Jan 1, 1970.
constexpr uint64_t WIN_FILETIME_2_UNIX_SYSTIME = 116444736000000000LL;

namespace librealsense
{
    namespace platform
    {
        class sensor_events : public ISensorEvents
        {
        public:
            virtual ~sensor_events() = default;

            explicit sensor_events(hid_callback callback) : m_cRef(0), _callback(callback) {}

            STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
            {
                if (ppv == NULL)
                {
                    return E_POINTER;
                }
                if (iid == __uuidof(IUnknown))
                {
                    *ppv = static_cast<IUnknown*>(this);
                }
                else if (iid == __uuidof(ISensorEvents))
                {
                    *ppv = static_cast<ISensorEvents*>(this);
                }
                else
                {
                    *ppv = NULL;
                    return E_NOINTERFACE;
                }
                AddRef();
                return S_OK;
            }

            STDMETHODIMP_(ULONG) AddRef()
            {
                return InterlockedIncrement(&m_cRef);
            }

            STDMETHODIMP_(ULONG) Release()
            {
                ULONG count = InterlockedDecrement(&m_cRef);
                if (count == 0)
                {
                    delete this;
                    return 0;
                }
                return count;
            }

            //
            // ISensorEvents methods.
            //

            STDMETHODIMP OnEvent(
                ISensor *pSensor,
                REFGUID eventID,
                IPortableDeviceValues *pEventData)
            {
                HRESULT hr = S_OK;

                // Handle custom events here.

                return hr;
            }

            STDMETHODIMP OnDataUpdated(ISensor *pSensor, ISensorDataReport *report)
            {
                if (NULL == report ||
                    NULL == pSensor)
                {
                    return E_INVALIDARG;
                }

                BSTR        fName{};
                SYSTEMTIME  sys_time;
                FILETIME    file_time;
                report->GetTimestamp(&sys_time);

                PROPVARIANT var = {};
                // Custom timestamp low
                auto hr = (report->GetSensorValue(SENSOR_DATA_TYPE_CUSTOM_VALUE1, &var));
                if (FAILED(hr)) return S_OK;
                auto customTimestampLow = var.ulVal;

                // Custom timestamp high
                CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_CUSTOM_VALUE2, &var));
                auto customTimestampHigh = var.ulVal;

                // Parse additional custom fields
                CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_CUSTOM_VALUE6, &var));
                uint8_t imu_count = var.bVal;
                CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_CUSTOM_VALUE7, &var));
                uint8_t usb_count = var.bVal;

                /* Retrieve sensor type - Sensor types are more specific groupings than sensor categories. Sensor type IDs are GUIDs that are defined in Sensors.h */

                SENSOR_TYPE_ID type{};

                CHECK_HR(pSensor->GetType(&type));

                double rawX, rawY, rawZ;

                if (type == SENSOR_TYPE_ACCELEROMETER_3D)
                {
                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_X_G, &var));
                    rawX = var.dblVal;

                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_Y_G, &var));
                    rawY = var.dblVal;

                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_Z_G, &var));
                    rawZ = var.dblVal;

                    static constexpr double accelerator_transform_factor = 1000.0;

                    rawX *= accelerator_transform_factor;
                    rawY *= accelerator_transform_factor;
                    rawZ *= accelerator_transform_factor;
                }
                else if (type == SENSOR_TYPE_GYROMETER_3D)
                {
                    // Raw X
                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ANGULAR_VELOCITY_X_DEGREES_PER_SECOND, &var));
                    rawX = var.dblVal;

                    // Raw Y
                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Y_DEGREES_PER_SECOND, &var));
                    rawY = var.dblVal;

                    // Raw Z
                    CHECK_HR(report->GetSensorValue(SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Z_DEGREES_PER_SECOND, &var));
                    rawZ = var.dblVal;

                    static constexpr double gyro_transform_factor = 10.0;

                    rawX *= gyro_transform_factor;
                    rawY *= gyro_transform_factor;
                    rawZ *= gyro_transform_factor;
                }
                else
                {
                    /* Unsupported sensor */
                    return S_FALSE;
                }

                PropVariantClear(&var);

                sensor_data d{};
                hid_sensor_data data{};
                // Populate HID IMU data - Header
                metadata_hid_raw meta_data{};
                meta_data.header.report_type = md_hid_report_type::hid_report_imu;
                meta_data.header.length = hid_header_size + metadata_imu_report_size;
                meta_data.header.timestamp = customTimestampLow | (customTimestampHigh < 32);
                // Payload:
                meta_data.report_type.imu_report.header.md_type_id = md_type::META_DATA_HID_IMU_REPORT_ID;
                meta_data.report_type.imu_report.header.md_size = metadata_imu_report_size;
                meta_data.report_type.imu_report.flags = static_cast<uint8_t>( md_hid_imu_attributes::custom_timestamp_attirbute |
                                                                                md_hid_imu_attributes::imu_counter_attribute |
                                                                                md_hid_imu_attributes::usb_counter_attribute);
                meta_data.report_type.imu_report.custom_timestamp = customTimestampLow | (customTimestampHigh < 32);
                meta_data.report_type.imu_report.imu_counter = imu_count;
                meta_data.report_type.imu_report.usb_counter = usb_count;

                data.x = static_cast<int16_t>(rawX);
                data.y = static_cast<int16_t>(rawY);
                data.z = static_cast<int16_t>(rawZ);
                data.ts_low = customTimestampLow;
                data.ts_high = customTimestampHigh;

                pSensor->GetFriendlyName(&fName);
                d.sensor.name = CW2A(fName);

                d.fo.pixels = &data;
                d.fo.metadata = &meta_data;
                d.fo.metadata_size = metadata_hid_raw_size;
                d.fo.frame_size = sizeof(data);
                d.fo.backend_time = 0;
                if (SystemTimeToFileTime(&sys_time, &file_time))
                {
                    auto ll_now = (LONGLONG)file_time.dwLowDateTime + ((LONGLONG)(file_time.dwHighDateTime) << 32LL) - WIN_FILETIME_2_UNIX_SYSTIME;
                    d.fo.backend_time = ll_now * 0.0001; //100 nano-sec to millisec
                }

                _callback(d);

                return S_OK;
            }

            STDMETHODIMP OnLeave(
                REFSENSOR_ID sensorID)
            {
                HRESULT hr = S_OK;

                // Perform any housekeeping tasks for the sensor that is leaving.
                // For example, if you have maintained a reference to the sensor,
                // release it now and set the pointer to NULL.

                return hr;
            }

            STDMETHODIMP OnStateChanged(
                ISensor* pSensor,
                SensorState state)
            {
                HRESULT hr = S_OK;

                if (NULL == pSensor)
                {
                    return E_INVALIDARG;
                }

                if (state == SENSOR_STATE_READY)
                {
                    wprintf_s(L"\nTime sensor is now ready.");
                }
                else if (state == SENSOR_STATE_ACCESS_DENIED)
                {
                    wprintf_s(L"\nNo permission for the time sensor.\n");
                    wprintf_s(L"Enable the sensor in the control panel.\n");
                }

                return hr;
            }

        private:
            long m_cRef;
            hid_callback _callback;
        };

        void wmf_hid_device::open(const std::vector<hid_profile>&iio_profiles)
        {
            try
            {
                for (auto& profile_to_open : iio_profiles)
                {
                    for (auto& connected_sensor : _connected_sensors)
                    {
                        if (profile_to_open.sensor_name == connected_sensor->get_sensor_name())
                        {
                            /* Set SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL sensor property to profile */
                            HRESULT hr = S_OK;
                            IPortableDeviceValues* pPropsToSet = NULL; // Input
                            IPortableDeviceValues* pPropsReturn = NULL; // Output

                            /* Create the input object */
                            CHECK_HR(CoCreateInstance(__uuidof(PortableDeviceValues), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pPropsToSet)));

                            /* Add the current report interval property */
                            hr = pPropsToSet->SetUnsignedIntegerValue(SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL, profile_to_open.frequency);
                            if (SUCCEEDED(hr))
                            {
                                // Setting a single property
                                hr = connected_sensor->get_sensor()->SetProperties(pPropsToSet, &pPropsReturn);
                                if (SUCCEEDED(hr))
                                {
                                    _opened_sensors.push_back(connected_sensor);
                                    pPropsReturn->Release();
                                }
                            }

                            pPropsToSet->Release();
                        }
                    }
                }
            }
            catch (...)
            {
                for (auto& connected_sensor : _connected_sensors)
                {
                    connected_sensor.reset();
                }
                _connected_sensors.clear();
                LOG_ERROR("Hid device is busy!");
                throw;
            }
        }

        void wmf_hid_device::close()
        {
            for (auto& open_sensor : _opened_sensors)
            {
                open_sensor.reset();
            }
            _opened_sensors.clear();
        }

        void wmf_hid_device::start_capture(hid_callback callback)
        {
            // Hack, start default profile
            _cb = new sensor_events(callback);
            ISensorEvents* sensorEvents = nullptr;
            CHECK_HR(_cb->QueryInterface(IID_PPV_ARGS(&sensorEvents)));

            for (auto& sensor : _opened_sensors)
            {
                CHECK_HR(sensor->start_capture(sensorEvents));
            }
        }

        void wmf_hid_device::stop_capture()
        {
            for (auto& sensor : _opened_sensors)
            {
                sensor->stop_capture();
            }
            _cb = nullptr;
        }

        std::vector<hid_sensor> wmf_hid_device::get_sensors()
        {
            std::vector<hid_sensor> sensors;

            HRESULT res = S_OK;
            BSTR fName{};

            for (auto& sensor : _opened_sensors)
            {
                sensors.push_back({ sensor->get_sensor_name() });
            }

            SysFreeString(fName);

            return sensors;
        }

        std::vector<uint8_t> wmf_hid_device::get_custom_report_data(const std::string & custom_sensor_name, const std::string & report_name, custom_sensor_report_field report_field)
        {
            return std::vector<uint8_t>();
        }

        void wmf_hid_device::foreach_hid_device(std::function<void(hid_device_info, CComPtr<ISensor>)> action)
        {
            /* Enumerate all HID devices and run action function on each device */
            try
            {
                CComPtr<ISensorManager> pSensorManager = nullptr;
                CComPtr<ISensorCollection> pSensorCollection = nullptr;
                CComPtr<ISensor> pSensor = nullptr;
                ULONG sensorCount = 0;
                HRESULT res{};

                CHECK_HR(CoCreateInstance(CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSensorManager)));

                /* Retrieves a collection containing all sensors associated with category SENSOR_CATEGORY_ALL */
                LOG_HR(res=pSensorManager->GetSensorsByCategory(SENSOR_CATEGORY_ALL, &pSensorCollection));
                if (SUCCEEDED(res))
                {
                    /* Retrieves the count of sensors in the collection */
                    CHECK_HR(pSensorCollection->GetCount(&sensorCount));

                    for (ULONG i = 0; i < sensorCount; i++)
                    {
                        /* Retrieves the sensor at the specified index in the collection */
                        if (SUCCEEDED(pSensorCollection->GetAt(i, &pSensor.p)))
                        {
                            /* Retrieve SENSOR_PROPERTY_FRIENDLY_NAME which is the sensor name that is intended to be seen by the user */
                            BSTR fName{};
                            LOG_HR(res = pSensor->GetFriendlyName(&fName));
                            if (FAILED(res)) fName= L"Unidentified HID sensor";

                            /* Retrieve SENSOR_PROPERTY_PERSISTENT_UNIQUE_ID which is a GUID that uniquely identifies the sensor on the current computer */
                            SENSOR_ID id{};
                            CHECK_HR(pSensor->GetID(&id));

                            /* Retrieve sensor type - Sensor types are more specific groupings than sensor categories. Sensor type IDs are GUIDs that are defined in Sensors.h */
                            SENSOR_TYPE_ID type{};
                            CHECK_HR(pSensor->GetType(&type));

                            CComPtr<IPortableDeviceValues> pValues = nullptr;  // Output
                            hid_device_info info{};

                            /* Retrieves multiple sensor properties */
                            auto hr = pSensor->GetProperties(nullptr, &pValues);
                            if (SUCCEEDED(hr))
                            {
                                /* Get the number of property returned */
                                DWORD propertyCount = 0;
                                hr = pValues->GetCount(&propertyCount);
                                if (SUCCEEDED(hr))
                                {
                                    PROPERTYKEY propertyKey;
                                    PROPVARIANT propertyValue = {};

                                    /* Loop through the properties */
                                    for (DWORD properyIndex = 0; properyIndex < propertyCount; properyIndex++)
                                    {
                                        // Get the value at the current index.
                                        hr = pValues->GetAt(properyIndex, &propertyKey, &propertyValue);
                                        if (SUCCEEDED(hr))
                                        {
                                            if (IsEqualPropertyKey(propertyKey, SENSOR_PROPERTY_DEVICE_PATH))
                                            {
                                                info.device_path = std::string(propertyValue.pwszVal, propertyValue.pwszVal + wcslen(propertyValue.pwszVal));
                                                info.id = std::string(fName, fName + wcslen(fName));

                                                uint16_t vid, pid, mi;
                                                std::string uid, guid;
                                                if (parse_usb_path_multiple_interface(vid, pid, mi, uid, info.device_path, guid))
                                                {
                                                    info.unique_id = "*";
                                                    info.pid = to_string() << std::hex << pid;
                                                    info.vid = to_string() << std::hex << vid;
                                                }
                                            }
                                            if (IsEqualPropertyKey(propertyKey, SENSOR_PROPERTY_SERIAL_NUMBER))
                                            {
                                                auto str = std::string(propertyValue.pwszVal, propertyValue.pwszVal + wcslen(propertyValue.pwszVal));
                                                std::transform(begin(str), end(str), begin(str), ::tolower);
                                                info.serial_number = str;
                                            }
                                        }

                                        PropVariantClear(&propertyValue);
                                    }
                                }
                            }

                            action(info, pSensor);

                            SysFreeString(fName);
                        }
                    }
                }
            }
            catch (...)
            {
                LOG_INFO("Could not enumerate HID devices!");
            }
        }
    }
}
<|endoftext|>