<commit_before>#include "Config.hpp"
#include "EventOutputQueue.hpp"
#include "ListHookedConsumer.hpp"
#include "ListHookedKeyboard.hpp"
#include "ListHookedPointing.hpp"
#include "PressDownKeys.hpp"
#include "RemapClass.hpp"
#include "VirtualKey.hpp"
#include "VK_IOHIDPOSTEVENT.hpp"

namespace org_pqrs_Karabiner {
  List* EventOutputQueue::queue_ = NULL;
  TimerWrapper EventOutputQueue::fire_timer_;
  Buttons EventOutputQueue::previousButtons_;

  void
  EventOutputQueue::initialize(IOWorkLoop& workloop)
  {
    queue_ = new List();
    fire_timer_.initialize(&workloop, NULL, EventOutputQueue::fire_timer_callback);
  }

  void
  EventOutputQueue::terminate(void)
  {
    fire_timer_.terminate();

    if (queue_) {
      delete queue_;
    }
  }

  // ----------------------------------------------------------------------
#define PUSH_TO_OUTPUTQUEUE {           \
    if (! queue_) return;               \
                                        \
    queue_->push_back(new Item(p));     \
    fire_timer_.setTimeoutMS(0, false); \
}
  void EventOutputQueue::push(const Params_KeyboardEventCallBack& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.eventType == EventType::DOWN) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_KeyboardSpecialEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.ex_iskeydown) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_RelativePointerEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.buttons != Buttons(0)) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_ScrollWheelEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    FlagStatus::globalFlagStatus().sticky_clear();
  }
  void EventOutputQueue::push(const Params_Wait& p) {
    PUSH_TO_OUTPUTQUEUE;
  }
#undef PUSH_TO_OUTPUTQUEUE

  // ----------------------------------------------------------------------
  unsigned int
  EventOutputQueue::calcDelay(const ParamsUnion& paramsUnion)
  {
    // ----------------------------------------
    {
      // Delay before and after modifier
      //
      // We need to wait before and after a modifier event because
      // events will be dropped by window server if
      // we send a mouse click event and a modifier event at the same time.
      //
      // For example, change middle click to command-left click by this autogen.
      // We need to put a wait on this setting in Mail.app.
      //    <autogen>
      //      __KeyToKey__
      //      PointingButton::MIDDLE, ModifierFlag::NONE,
      //      PointingButton::LEFT, ModifierFlag::COMMAND_L,
      //    </autogen>
      //
      if (paramsUnion.isModifier()) {
        return Config::get_wait_before_and_after_a_modifier_key_event();
      }
    }

    // ----------------------------------------
    {
      // Delay before and after modifier
      //
      // We need to wait before and after a pointing device click event because
      // events will be dropped by processes (eg. NetBeans) if
      // we send press button event and release button event at the same time.
      //

      Params_RelativePointerEventCallback* params = paramsUnion.get_Params_RelativePointerEventCallback();
      if (params) {
        if (params->buttons != previousButtons_) {
          previousButtons_ = params->buttons;
          return Config::get_wait_before_and_after_a_click_event();
        }
      }
    }

    // ----------------------------------------
    return 0;
  }

  namespace {
    unsigned int maxDelay(unsigned int v1, unsigned int v2) {
      if (v1 > v2) {
        return v1;
      } else {
        return v2;
      }
    }
  }

  void
  EventOutputQueue::fire_timer_callback(OSObject* /* owner */, IOTimerEventSource* /* sender */)
  {
    if (! queue_) return;

    // IOLOG_DEVEL("EventOutputQueue::fire queue_->size = %d\n", static_cast<int>(queue_->size()));

    Item* p = static_cast<Item*>(queue_->front());
    if (! p) return;

    // Delay after modifier or click.
    unsigned int delay = calcDelay(p->params);

    // ----------------------------------------
    switch ((p->params).type) {
      case ParamsUnion::KEYBOARD:
      {
        Params_KeyboardEventCallBack* params = (p->params).get_Params_KeyboardEventCallBack();
        if (params) {
          bool handled = VirtualKey::handleAfterEnqueued(*params);
          if (! handled) {
            ListHookedKeyboard::instance().apply(*params);
          }
        }

        // We need to wait at least 1ms in order to avoid changing key sequence order randomly.
        // (If VMware Fusion's driver is installed, the wrong order issue will be happen.)
        delay = maxDelay(delay, 1);
        break;
      }
      case ParamsUnion::KEYBOARD_SPECIAL:
      {
        Params_KeyboardSpecialEventCallback* params = (p->params).get_Params_KeyboardSpecialEventCallback();
        if (params) {
          if (! ListHookedConsumer::instance().apply(*params)) {
            // If there is no consumer device, we send an event as a software key.
            VirtualKey::VK_IOHIDPOSTEVENT::post(*params);
          }
        }

        // We need to wait at least 1ms in order to avoid changing key sequence order randomly.
        // (If VMware Fusion's driver is installed, the wrong order issue will be happen.)
        delay = maxDelay(delay, 1);
        break;
      }
      case ParamsUnion::RELATIVE_POINTER:
      {
        Params_RelativePointerEventCallback* params = (p->params).get_Params_RelativePointerEventCallback();
        if (params) {
          ListHookedPointing::instance().apply(*params);
        }
        break;
      }
      case ParamsUnion::SCROLL_WHEEL:
      {
        Params_ScrollWheelEventCallback* params = (p->params).get_Params_ScrollWheelEventCallback();
        if (params) {
          ListHookedPointing::instance().apply(*params);
        }
        break;
      }
      case ParamsUnion::WAIT:
      {
        Params_Wait* params = (p->params).get_Params_Wait();
        if (params) {
          delay = maxDelay(delay, static_cast<unsigned int>(params->milliseconds));
        }
        break;
      }
      case ParamsUnion::UPDATE_FLAGS:
        // We need to wait at least 1ms in order to avoid changing key sequence order randomly.
        // (If VMware Fusion's driver is installed, the wrong order issue will be happen.)
        delay = maxDelay(delay, 1);
        break;
    }

    queue_->pop_front();

    // ----------------------------------------
    // Set timeout for next event.

    // Delay before modifier and click.
    Item* next = static_cast<Item*>(queue_->front());
    if (! next) return;
    delay = maxDelay(delay, calcDelay(next->params));

    fire_timer_.setTimeoutMS(delay);
  }

  // ======================================================================
  Flags EventOutputQueue::FireModifiers::lastFlags_(0);

  void
  EventOutputQueue::FireModifiers::fire(Flags toFlags, KeyboardType keyboardType)
  {
    if (lastFlags_ == toFlags) return;

    // ------------------------------------------------------------
    // At first we handle KeyUp events and handle KeyDown events next.
    // We need to end KeyDown at Command+Space to Option_L+Shift_L.
    //
    // When Option_L+Shift_L has a meaning (switch input language at Windows),
    // it does not works well when the last is KeyUp of Command.

    // ------------------------------------------------------------
    // KeyUp
    for (size_t i = 0; i < FlagStatus::globalFlagStatus().itemSize(); ++i) {
      ModifierFlag flag = FlagStatus::globalFlagStatus().getFlag(i);

      // Skipping ModifierFlag::NUMPAD
      if (flag.getKeyCode() == KeyCode::VK_NONE) continue;
      // Skipping virtual modifiers.
      if (flag.getRawBits() == 0) continue;

      // ----------------------------------------
      if (! lastFlags_.isOn(flag)) continue;
      if (toFlags.isOn(flag)) continue;

      lastFlags_.remove(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    // KeyDown
    for (size_t i = 0; i < FlagStatus::globalFlagStatus().itemSize(); ++i) {
      ModifierFlag flag = FlagStatus::globalFlagStatus().getFlag(i);

      // Skipping ModifierFlag::NUMPAD
      if (flag.getKeyCode() == KeyCode::VK_NONE) continue;
      // Skipping virtual modifiers.
      if (flag.getRawBits() == 0) continue;

      // ----------------------------------------
      if (! toFlags.isOn(flag)) continue;
      if (lastFlags_.isOn(flag)) continue;

      lastFlags_.add(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    lastFlags_ = toFlags;
  }

  // ======================================================================
  void
  EventOutputQueue::FireKey::fire(const Params_KeyboardEventCallBack& params)
  {
    if (VirtualKey::handle(params)) return;

    // ------------------------------------------------------------
    KeyCode newkeycode = params.key;
    Flags newflags = params.flags;

    KeyCode::reverseNormalizeKey(newkeycode, newflags, params.eventType, params.keyboardType);

    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (newkeycode == KeyCode::VK_NONE ||
        newkeycode == KeyCode::VK_PSEUDO_KEY) {
      return;
    }
    if (newkeycode.isModifier() && newkeycode.getModifierFlag().getRawBits() == 0) {
      // virtual modifiers.
      return;
    }

    FireModifiers::fire(newflags, params.keyboardType);

    if (params.eventType == EventType::DOWN || params.eventType == EventType::UP) {
      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(params.eventType, newflags, newkeycode,
                                                                                params.charCode, params.charSet, params.origCharCode, params.origCharSet,
                                                                                params.keyboardType, params.repeat));
      if (ptr) {
        EventOutputQueue::push(*ptr);
      }

      if (! params.repeat) {
        if (params.eventType == EventType::DOWN) {
          PressDownKeys::add(newkeycode, params.keyboardType);
        } else {
          PressDownKeys::remove(newkeycode, params.keyboardType);
        }
      }
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireConsumer::fire(const Params_KeyboardSpecialEventCallback& params)
  {
    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (params.key == ConsumerKeyCode::VK_NONE ||
        params.key == ConsumerKeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire();

    Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(params.eventType, params.flags, params.key, params.repeat));
    if (ptr) {
      EventOutputQueue::push(*ptr);
    }
  }

  // ======================================================================
  Buttons EventOutputQueue::FireRelativePointer::lastButtons_(0);

  void
  EventOutputQueue::FireRelativePointer::fire(Buttons toButtons, int dx, int dy)
  {
    // When changing space to command+left click,
    //   __KeyToKey__ KeyCode::SPACE, PointingButton::LEFT, ModifierFlag::COMMAND_L
    //
    // We need to release command key after left click was released as follows.
    //   (1) KeyDown   Command_L
    //   (2) MouseDown Left
    //   (3) MouseUp   Left
    //   (4) KeyUp     Command_L
    //
    // For mouse drag support, command modifier is increased as normal (not temporary_increase).
    // Therefore, command modifier is decreased when space key is released.
    // If we call FireModifiers::fire() at the begining of this function,
    // input events are fired as follows order.
    //   (1) KeyDown   Command_L
    //   (2) MouseDown Left
    //   (3) KeyUp     Command_L
    //   (4) MouseUp   Left
    //
    // It's not desired order.
    // So, we call FireModifiers::fire() at the end of this function when button is released.

    Buttons releasedButtons = lastButtons_;
    releasedButtons.remove(toButtons);
    bool isButtonReleased = ! releasedButtons.isNONE();

    if (! isButtonReleased) {
      FireModifiers::fire();
    }

    // Sending button event
    if (lastButtons_ != toButtons) {
      lastButtons_ = toButtons;

      Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, 0, 0, PointingButton::NONE, false));
      if (! ptr) return;
      Params_RelativePointerEventCallback& params = *ptr;

      EventOutputQueue::push(params);
    }

    // Sending cursor
    if (dx != 0 || dy != 0) {
      Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, dx, dy, PointingButton::NONE, false));
      if (! ptr) return;
      Params_RelativePointerEventCallback& params = *ptr;

      EventOutputQueue::push(params);
    }

    if (isButtonReleased) {
      FireModifiers::fire();
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireScrollWheel::fire(const Params_ScrollWheelEventCallback& params)
  {
    FireModifiers::fire();
    EventOutputQueue::push(params);
  }

  void
  EventOutputQueue::FireScrollWheel::fire(int delta1, int delta2)
  {
    short deltaAxis1;
    short deltaAxis2;
    IOFixed fixedDelta1;
    IOFixed fixedDelta2;
    SInt32 pointDelta1;
    SInt32 pointDelta2;

    int relative2scroll_rate = Config::get_essential_config(BRIDGE_ESSENTIAL_CONFIG_INDEX_pointing_relative2scroll_rate);

    deltaAxis1 = (delta1 * relative2scroll_rate) / 1024 / DELTA_SCALE;
    deltaAxis2 = (delta2 * relative2scroll_rate) / 1024 / DELTA_SCALE;

    fixedDelta1 = (delta1 * relative2scroll_rate) * (POINTING_FIXED_SCALE / 1024) / DELTA_SCALE;
    fixedDelta2 = (delta2 * relative2scroll_rate) * (POINTING_FIXED_SCALE / 1024) / DELTA_SCALE;

    pointDelta1 = (delta1 * POINTING_POINT_SCALE * relative2scroll_rate) / 1024 / DELTA_SCALE;
    pointDelta2 = (delta2 * POINTING_POINT_SCALE * relative2scroll_rate) / 1024 / DELTA_SCALE;

    // see IOHIDSystem/IOHIDevicePrivateKeys.h about options.
    const int kScrollTypeContinuous_ = 0x0001;
    int options = kScrollTypeContinuous_;

    Params_ScrollWheelEventCallback::auto_ptr ptr(Params_ScrollWheelEventCallback::alloc(deltaAxis1,  deltaAxis2, 0,
                                                                                fixedDelta1, fixedDelta2, 0,
                                                                                pointDelta1, pointDelta2, 0,
                                                                                options));
    if (! ptr) return;
    EventOutputQueue::FireScrollWheel::fire(*ptr);
  }

  // ======================================================================
  void
  EventOutputQueue::FireWait::fire(const Params_Wait& params)
  {
    EventOutputQueue::push(params);
  }
}
<commit_msg>fix EventOutputQueue::FireModifiers::fire (remove NUMPAD from lastFlags_)<commit_after>#include "Config.hpp"
#include "EventOutputQueue.hpp"
#include "ListHookedConsumer.hpp"
#include "ListHookedKeyboard.hpp"
#include "ListHookedPointing.hpp"
#include "PressDownKeys.hpp"
#include "RemapClass.hpp"
#include "VirtualKey.hpp"
#include "VK_IOHIDPOSTEVENT.hpp"

namespace org_pqrs_Karabiner {
  List* EventOutputQueue::queue_ = NULL;
  TimerWrapper EventOutputQueue::fire_timer_;
  Buttons EventOutputQueue::previousButtons_;

  void
  EventOutputQueue::initialize(IOWorkLoop& workloop)
  {
    queue_ = new List();
    fire_timer_.initialize(&workloop, NULL, EventOutputQueue::fire_timer_callback);
  }

  void
  EventOutputQueue::terminate(void)
  {
    fire_timer_.terminate();

    if (queue_) {
      delete queue_;
    }
  }

  // ----------------------------------------------------------------------
#define PUSH_TO_OUTPUTQUEUE {           \
    if (! queue_) return;               \
                                        \
    queue_->push_back(new Item(p));     \
    fire_timer_.setTimeoutMS(0, false); \
}
  void EventOutputQueue::push(const Params_KeyboardEventCallBack& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.eventType == EventType::DOWN) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_KeyboardSpecialEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.ex_iskeydown) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_RelativePointerEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.buttons != Buttons(0)) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_ScrollWheelEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    FlagStatus::globalFlagStatus().sticky_clear();
  }
  void EventOutputQueue::push(const Params_Wait& p) {
    PUSH_TO_OUTPUTQUEUE;
  }
#undef PUSH_TO_OUTPUTQUEUE

  // ----------------------------------------------------------------------
  unsigned int
  EventOutputQueue::calcDelay(const ParamsUnion& paramsUnion)
  {
    // ----------------------------------------
    {
      // Delay before and after modifier
      //
      // We need to wait before and after a modifier event because
      // events will be dropped by window server if
      // we send a mouse click event and a modifier event at the same time.
      //
      // For example, change middle click to command-left click by this autogen.
      // We need to put a wait on this setting in Mail.app.
      //    <autogen>
      //      __KeyToKey__
      //      PointingButton::MIDDLE, ModifierFlag::NONE,
      //      PointingButton::LEFT, ModifierFlag::COMMAND_L,
      //    </autogen>
      //
      if (paramsUnion.isModifier()) {
        return Config::get_wait_before_and_after_a_modifier_key_event();
      }
    }

    // ----------------------------------------
    {
      // Delay before and after modifier
      //
      // We need to wait before and after a pointing device click event because
      // events will be dropped by processes (eg. NetBeans) if
      // we send press button event and release button event at the same time.
      //

      Params_RelativePointerEventCallback* params = paramsUnion.get_Params_RelativePointerEventCallback();
      if (params) {
        if (params->buttons != previousButtons_) {
          previousButtons_ = params->buttons;
          return Config::get_wait_before_and_after_a_click_event();
        }
      }
    }

    // ----------------------------------------
    return 0;
  }

  namespace {
    unsigned int maxDelay(unsigned int v1, unsigned int v2) {
      if (v1 > v2) {
        return v1;
      } else {
        return v2;
      }
    }
  }

  void
  EventOutputQueue::fire_timer_callback(OSObject* /* owner */, IOTimerEventSource* /* sender */)
  {
    if (! queue_) return;

    // IOLOG_DEVEL("EventOutputQueue::fire queue_->size = %d\n", static_cast<int>(queue_->size()));

    Item* p = static_cast<Item*>(queue_->front());
    if (! p) return;

    // Delay after modifier or click.
    unsigned int delay = calcDelay(p->params);

    // ----------------------------------------
    switch ((p->params).type) {
      case ParamsUnion::KEYBOARD:
      {
        Params_KeyboardEventCallBack* params = (p->params).get_Params_KeyboardEventCallBack();
        if (params) {
          bool handled = VirtualKey::handleAfterEnqueued(*params);
          if (! handled) {
            ListHookedKeyboard::instance().apply(*params);
          }
        }

        // We need to wait at least 1ms in order to avoid changing key sequence order randomly.
        // (If VMware Fusion's driver is installed, the wrong order issue will be happen.)
        delay = maxDelay(delay, 1);
        break;
      }
      case ParamsUnion::KEYBOARD_SPECIAL:
      {
        Params_KeyboardSpecialEventCallback* params = (p->params).get_Params_KeyboardSpecialEventCallback();
        if (params) {
          if (! ListHookedConsumer::instance().apply(*params)) {
            // If there is no consumer device, we send an event as a software key.
            VirtualKey::VK_IOHIDPOSTEVENT::post(*params);
          }
        }

        // We need to wait at least 1ms in order to avoid changing key sequence order randomly.
        // (If VMware Fusion's driver is installed, the wrong order issue will be happen.)
        delay = maxDelay(delay, 1);
        break;
      }
      case ParamsUnion::RELATIVE_POINTER:
      {
        Params_RelativePointerEventCallback* params = (p->params).get_Params_RelativePointerEventCallback();
        if (params) {
          ListHookedPointing::instance().apply(*params);
        }
        break;
      }
      case ParamsUnion::SCROLL_WHEEL:
      {
        Params_ScrollWheelEventCallback* params = (p->params).get_Params_ScrollWheelEventCallback();
        if (params) {
          ListHookedPointing::instance().apply(*params);
        }
        break;
      }
      case ParamsUnion::WAIT:
      {
        Params_Wait* params = (p->params).get_Params_Wait();
        if (params) {
          delay = maxDelay(delay, static_cast<unsigned int>(params->milliseconds));
        }
        break;
      }
      case ParamsUnion::UPDATE_FLAGS:
        // We need to wait at least 1ms in order to avoid changing key sequence order randomly.
        // (If VMware Fusion's driver is installed, the wrong order issue will be happen.)
        delay = maxDelay(delay, 1);
        break;
    }

    queue_->pop_front();

    // ----------------------------------------
    // Set timeout for next event.

    // Delay before modifier and click.
    Item* next = static_cast<Item*>(queue_->front());
    if (! next) return;
    delay = maxDelay(delay, calcDelay(next->params));

    fire_timer_.setTimeoutMS(delay);
  }

  // ======================================================================
  Flags EventOutputQueue::FireModifiers::lastFlags_(0);

  void
  EventOutputQueue::FireModifiers::fire(Flags toFlags, KeyboardType keyboardType)
  {
    if (lastFlags_ == toFlags) return;

    // ------------------------------------------------------------
    // At first we handle KeyUp events and handle KeyDown events next.
    // We need to end KeyDown at Command+Space to Option_L+Shift_L.
    //
    // When Option_L+Shift_L has a meaning (switch input language at Windows),
    // it does not works well when the last is KeyUp of Command.

    // ------------------------------------------------------------
    // KeyUp
    for (size_t i = 0; i < FlagStatus::globalFlagStatus().itemSize(); ++i) {
      ModifierFlag flag = FlagStatus::globalFlagStatus().getFlag(i);

      // Skipping ModifierFlag::NUMPAD
      if (flag.getKeyCode() == KeyCode::VK_NONE) continue;
      // Skipping virtual modifiers.
      if (flag.getRawBits() == 0) continue;

      // ----------------------------------------
      if (! lastFlags_.isOn(flag)) continue;
      if (toFlags.isOn(flag)) continue;

      lastFlags_.remove(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    // KeyDown
    for (size_t i = 0; i < FlagStatus::globalFlagStatus().itemSize(); ++i) {
      ModifierFlag flag = FlagStatus::globalFlagStatus().getFlag(i);

      // Skipping ModifierFlag::NUMPAD
      if (flag.getKeyCode() == KeyCode::VK_NONE) continue;
      // Skipping virtual modifiers.
      if (flag.getRawBits() == 0) continue;

      // ----------------------------------------
      if (! toFlags.isOn(flag)) continue;
      if (lastFlags_.isOn(flag)) continue;

      lastFlags_.add(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireKey::fire(const Params_KeyboardEventCallBack& params)
  {
    if (VirtualKey::handle(params)) return;

    // ------------------------------------------------------------
    KeyCode newkeycode = params.key;
    Flags newflags = params.flags;

    KeyCode::reverseNormalizeKey(newkeycode, newflags, params.eventType, params.keyboardType);

    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (newkeycode == KeyCode::VK_NONE ||
        newkeycode == KeyCode::VK_PSEUDO_KEY) {
      return;
    }
    if (newkeycode.isModifier() && newkeycode.getModifierFlag().getRawBits() == 0) {
      // virtual modifiers.
      return;
    }

    FireModifiers::fire(newflags, params.keyboardType);

    if (params.eventType == EventType::DOWN || params.eventType == EventType::UP) {
      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(params.eventType, newflags, newkeycode,
                                                                                params.charCode, params.charSet, params.origCharCode, params.origCharSet,
                                                                                params.keyboardType, params.repeat));
      if (ptr) {
        EventOutputQueue::push(*ptr);
      }

      if (! params.repeat) {
        if (params.eventType == EventType::DOWN) {
          PressDownKeys::add(newkeycode, params.keyboardType);
        } else {
          PressDownKeys::remove(newkeycode, params.keyboardType);
        }
      }
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireConsumer::fire(const Params_KeyboardSpecialEventCallback& params)
  {
    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (params.key == ConsumerKeyCode::VK_NONE ||
        params.key == ConsumerKeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire();

    Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(params.eventType, params.flags, params.key, params.repeat));
    if (ptr) {
      EventOutputQueue::push(*ptr);
    }
  }

  // ======================================================================
  Buttons EventOutputQueue::FireRelativePointer::lastButtons_(0);

  void
  EventOutputQueue::FireRelativePointer::fire(Buttons toButtons, int dx, int dy)
  {
    // When changing space to command+left click,
    //   __KeyToKey__ KeyCode::SPACE, PointingButton::LEFT, ModifierFlag::COMMAND_L
    //
    // We need to release command key after left click was released as follows.
    //   (1) KeyDown   Command_L
    //   (2) MouseDown Left
    //   (3) MouseUp   Left
    //   (4) KeyUp     Command_L
    //
    // For mouse drag support, command modifier is increased as normal (not temporary_increase).
    // Therefore, command modifier is decreased when space key is released.
    // If we call FireModifiers::fire() at the begining of this function,
    // input events are fired as follows order.
    //   (1) KeyDown   Command_L
    //   (2) MouseDown Left
    //   (3) KeyUp     Command_L
    //   (4) MouseUp   Left
    //
    // It's not desired order.
    // So, we call FireModifiers::fire() at the end of this function when button is released.

    Buttons releasedButtons = lastButtons_;
    releasedButtons.remove(toButtons);
    bool isButtonReleased = ! releasedButtons.isNONE();

    if (! isButtonReleased) {
      FireModifiers::fire();
    }

    // Sending button event
    if (lastButtons_ != toButtons) {
      lastButtons_ = toButtons;

      Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, 0, 0, PointingButton::NONE, false));
      if (! ptr) return;
      Params_RelativePointerEventCallback& params = *ptr;

      EventOutputQueue::push(params);
    }

    // Sending cursor
    if (dx != 0 || dy != 0) {
      Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, dx, dy, PointingButton::NONE, false));
      if (! ptr) return;
      Params_RelativePointerEventCallback& params = *ptr;

      EventOutputQueue::push(params);
    }

    if (isButtonReleased) {
      FireModifiers::fire();
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireScrollWheel::fire(const Params_ScrollWheelEventCallback& params)
  {
    FireModifiers::fire();
    EventOutputQueue::push(params);
  }

  void
  EventOutputQueue::FireScrollWheel::fire(int delta1, int delta2)
  {
    short deltaAxis1;
    short deltaAxis2;
    IOFixed fixedDelta1;
    IOFixed fixedDelta2;
    SInt32 pointDelta1;
    SInt32 pointDelta2;

    int relative2scroll_rate = Config::get_essential_config(BRIDGE_ESSENTIAL_CONFIG_INDEX_pointing_relative2scroll_rate);

    deltaAxis1 = (delta1 * relative2scroll_rate) / 1024 / DELTA_SCALE;
    deltaAxis2 = (delta2 * relative2scroll_rate) / 1024 / DELTA_SCALE;

    fixedDelta1 = (delta1 * relative2scroll_rate) * (POINTING_FIXED_SCALE / 1024) / DELTA_SCALE;
    fixedDelta2 = (delta2 * relative2scroll_rate) * (POINTING_FIXED_SCALE / 1024) / DELTA_SCALE;

    pointDelta1 = (delta1 * POINTING_POINT_SCALE * relative2scroll_rate) / 1024 / DELTA_SCALE;
    pointDelta2 = (delta2 * POINTING_POINT_SCALE * relative2scroll_rate) / 1024 / DELTA_SCALE;

    // see IOHIDSystem/IOHIDevicePrivateKeys.h about options.
    const int kScrollTypeContinuous_ = 0x0001;
    int options = kScrollTypeContinuous_;

    Params_ScrollWheelEventCallback::auto_ptr ptr(Params_ScrollWheelEventCallback::alloc(deltaAxis1,  deltaAxis2, 0,
                                                                                fixedDelta1, fixedDelta2, 0,
                                                                                pointDelta1, pointDelta2, 0,
                                                                                options));
    if (! ptr) return;
    EventOutputQueue::FireScrollWheel::fire(*ptr);
  }

  // ======================================================================
  void
  EventOutputQueue::FireWait::fire(const Params_Wait& params)
  {
    EventOutputQueue::push(params);
  }
}
<|endoftext|>


<commit_before>#ifndef DEVICEFILTER_HPP
#define DEVICEFILTER_HPP

#include "bridge.h"
#include "CommonData.hpp"
#include "DeviceFilter.hpp"
#include "IOLogWrapper.hpp"
#include "RemapFilterBase.hpp"

namespace org_pqrs_Karabiner {
  namespace RemapFilter {
    class DeviceFilter : public RemapFilterBase {
    public:
      DeviceFilter(unsigned int type) : RemapFilterBase(type) {};

      void initialize(const unsigned int* vec, size_t length) {
        for (size_t i = 0; i < length - 2; i += 3) {
          targets_.push_back(DeviceIdentifier(DeviceVendor(vec[i]),
                                              DeviceProduct(vec[i + 1]),
                                              DeviceLocation(vec[i + 2])));
        }

        if (length % 3 > 0) {
          IOLOG_WARN("Invalid length(%d) in BRIDGE_FILTERTYPE_DEVICE_*\n", static_cast<int>(length));
        }
      }

      bool isblocked(void) {
        if (get_type() == BRIDGE_FILTERTYPE_DEVICE_NOT ||
            get_type() == BRIDGE_FILTERTYPE_DEVICE_ONLY) {

          bool isnot = (get_type() == BRIDGE_FILTERTYPE_DEVICE_NOT);

          for (size_t i = 0; i < targets_.size(); ++i) {
            DeviceIdentifier& v = targets_[i];
            if (CommonData::getcurrent_deviceIdentifier().isEqual(v)) {
              return isnot ? true : false;
            }
          }

          return isnot ? false : true;
        }

        IOLOG_ERROR("DeviceFilter::isblocked unknown type(%d)\n", get_type());
        return false;
      }

    private:
      Vector_DeviceIdentifier targets_;
    };
  }
}

#endif
<commit_msg>clean up #include<commit_after>#ifndef DEVICEFILTER_HPP
#define DEVICEFILTER_HPP

#include "CommonData.hpp"
#include "RemapFilterBase.hpp"

namespace org_pqrs_Karabiner {
  namespace RemapFilter {
    class DeviceFilter : public RemapFilterBase {
    public:
      DeviceFilter(unsigned int type) : RemapFilterBase(type) {};

      void initialize(const unsigned int* vec, size_t length) {
        for (size_t i = 0; i < length - 2; i += 3) {
          targets_.push_back(DeviceIdentifier(DeviceVendor(vec[i]),
                                              DeviceProduct(vec[i + 1]),
                                              DeviceLocation(vec[i + 2])));
        }

        if (length % 3 > 0) {
          IOLOG_WARN("Invalid length(%d) in BRIDGE_FILTERTYPE_DEVICE_*\n", static_cast<int>(length));
        }
      }

      bool isblocked(void) {
        if (get_type() == BRIDGE_FILTERTYPE_DEVICE_NOT ||
            get_type() == BRIDGE_FILTERTYPE_DEVICE_ONLY) {

          bool isnot = (get_type() == BRIDGE_FILTERTYPE_DEVICE_NOT);

          for (size_t i = 0; i < targets_.size(); ++i) {
            DeviceIdentifier& v = targets_[i];
            if (CommonData::getcurrent_deviceIdentifier().isEqual(v)) {
              return isnot ? true : false;
            }
          }

          return isnot ? false : true;
        }

        IOLOG_ERROR("DeviceFilter::isblocked unknown type(%d)\n", get_type());
        return false;
      }

    private:
      Vector_DeviceIdentifier targets_;
    };
  }
}

#endif
<|endoftext|>


<commit_before>/*
 * DesktopSessionLauncher.cpp
 *
 * Copyright (C) 2009-18 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "DesktopSessionLauncher.hpp"

#include <iostream>

#include <boost/bind.hpp>

#include <core/http/Request.hpp>
#include <core/http/Response.hpp>
#include <core/http/TcpIpBlockingClient.hpp>

#include <core/WaitUtils.hpp>
#include <core/FileSerializer.hpp>
#include <core/system/Environment.hpp>
#include <core/system/ParentProcessMonitor.hpp>
#include <core/r_util/RUserData.hpp>

#include <QPushButton>

#include "DesktopOptions.hpp"
#include "DesktopSlotBinders.hpp"
#include "DesktopActivationOverlay.hpp"

#define RUN_DIAGNOSTICS_LOG(message) if (desktop::options().runDiagnostics()) \
             std::cout << (message) << std::endl;

using namespace rstudio::core;

namespace rstudio {
namespace desktop {

namespace {

std::string s_launcherToken;

void launchProcess(const std::string& absPath,
                   const QStringList& argList,
                   QProcess** ppProc)
{
   QProcess* process = new QProcess();
   process->setProgram(QString::fromStdString(absPath));
   process->setArguments(argList);
   if (options().runDiagnostics())
      process->setProcessChannelMode(QProcess::ForwardedChannels);

#ifdef _WIN32
   process->setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments* cpa)
   {
      cpa->flags |= CREATE_NEW_PROCESS_GROUP;
   });
#endif

   process->start();
   *ppProc = process;
}

FilePath abendLogPath()
{
   return desktop::userLogPath().complete("rsession_abort_msg.log");
}

void logEnvVar(const std::string& name)
{
   std::string value = core::system::getenv(name);
   if (!value.empty())
      RUN_DIAGNOSTICS_LOG("  " + name + "=" + value);
}

} // anonymous namespace

void SessionLauncher::launchFirstSession(const core::FilePath& installPath,
                                         bool devMode,
                                         const QStringList& arguments)
{
   connect(&activation(), &DesktopActivation::launchFirstSession,
           this, &SessionLauncher::onLaunchFirstSession, Qt::QueuedConnection);
   connect(&activation(), &DesktopActivation::launchError,
           this, &SessionLauncher::onLaunchError, Qt::QueuedConnection);
   activation().getInitialLicense(arguments, installPath, devMode);
}

Error SessionLauncher::launchFirstSession()
{
   // build a new new launch context
   QString host, port;
   QStringList argList;
   QUrl url;
   buildLaunchContext(&host, &port, &argList, &url);

   // show help home on first run
   argList.push_back(QString::fromUtf8("--show-help-home"));
   argList.push_back(QString::fromUtf8("1"));

   RUN_DIAGNOSTICS_LOG("\nAttempting to launch R session...");
   logEnvVar("RSTUDIO_WHICH_R");
   logEnvVar("R_HOME");
   logEnvVar("R_DOC_DIR");
   logEnvVar("R_INCLUDE_DIR");
   logEnvVar("R_SHARE_DIR");
   logEnvVar("R_LIBS");
   logEnvVar("R_LIBS_USER");
   logEnvVar("DYLD_LIBRARY_PATH");
   logEnvVar("LD_LIBRARY_PATH");
   logEnvVar("PATH");
   logEnvVar("HOME");
   logEnvVar("R_USER");

   // launch the process
   Error error = launchSession(argList, &pRSessionProcess_);
   if (error)
     return error;

   RUN_DIAGNOSTICS_LOG("\nR session launched, "
                           "attempting to connect on port "
                           + port.toStdString() +
                           "...");

   pMainWindow_ = new MainWindow(url);
   pMainWindow_->setAttribute(Qt::WA_DeleteOnClose);
   pMainWindow_->setSessionLauncher(this);
   pMainWindow_->setSessionProcess(pRSessionProcess_);
   pMainWindow_->setAppLauncher(pAppLaunch_);
   pAppLaunch_->setActivationWindow(pMainWindow_);

   desktop::options().restoreMainWindowBounds(pMainWindow_);

   RUN_DIAGNOSTICS_LOG("\nConnected to R session, attempting to initialize...\n");

   // one-time workbench initialized hook for startup file association
   if (!filename_.isNull() && !filename_.isEmpty())
   {
      StringSlotBinder* filenameBinder = new StringSlotBinder(filename_);
      pMainWindow_->connect(pMainWindow_,
                            SIGNAL(firstWorkbenchInitialized()),
                            filenameBinder,
                            SLOT(trigger()));
      pMainWindow_->connect(filenameBinder,
                            SIGNAL(triggered(QString)),
                            pMainWindow_,
                            SLOT(openFileInRStudio(QString)));
   }

   pMainWindow_->connect(pAppLaunch_,
                         SIGNAL(openFileRequest(QString)),
                         pMainWindow_,
                         SLOT(openFileInRStudio(QString)));

   pMainWindow_->connect(pRSessionProcess_,
                         SIGNAL(finished(int,QProcess::ExitStatus)),
                         this, SLOT(onRSessionExited(int,QProcess::ExitStatus)));

   pMainWindow_->connect(&activation(),
                         SIGNAL(licenseLost(QString)),
                         pMainWindow_,
                         SLOT(onLicenseLost(QString)));

   pMainWindow_->connect(&activation(), &DesktopActivation::updateLicenseWarningBar,
                         pMainWindow_, &MainWindow::onUpdateLicenseWarningBar);

   // show the window (but don't if we are doing a --run-diagnostics)
   if (!options().runDiagnostics())
   {
      finalPlatformInitialize(pMainWindow_);
      pMainWindow_->show();
      desktop::activation().setMainWindow(pMainWindow_);
      pAppLaunch_->activateWindow();
      pMainWindow_->loadUrl(url);
   }
   qApp->setQuitOnLastWindowClosed(true);
   return Success();
}

void SessionLauncher::closeAllSatellites()
{
   QWidgetList topLevels = QApplication::topLevelWidgets();
   for (auto pWindow : topLevels)
   {
      if (pWindow != pMainWindow_)
        pWindow->close();
   }
}

void SessionLauncher::onRSessionExited(int, QProcess::ExitStatus)
{
   // if this is a verify-installation session then just quit
   if (options().runDiagnostics())
   {
      pMainWindow_->quit();
      return;
   }

   int pendingQuit = pMainWindow_->collectPendingQuitRequest();

   // if there was no pending quit set then this is a crash
   if (pendingQuit == PendingQuitNone)
   {
      closeAllSatellites();
      pMainWindow_->webView()->webPage()->runJavaScript(
               QString::fromUtf8("window.desktopHooks.notifyRCrashed()"));

      if (abendLogPath().exists())
      {
         showMessageBox(QMessageBox::Critical,
                        pMainWindow_,
                        desktop::activation().editionName(),
                        launchFailedErrorMessage(), QString());
      }
   }

   // quit and exit means close the main window
   else if (pendingQuit == PendingQuitAndExit)
   {
      pMainWindow_->quit();
   }

   // otherwise this is a restart so we need to launch the next session
   else
   {
      if (!activation().allowProductUsage())
      {
         std::string message = "Unable to obtain a license. Please restart RStudio to try again.";
         std::string licenseMessage = activation().currentLicenseStateMessage();
         if (licenseMessage.empty())
            licenseMessage = "None Available";
         message += "\n\nDetails: ";
         message += licenseMessage;
         showMessageBox(QMessageBox::Critical,
                        pMainWindow_,
                        desktop::activation().editionName(),
                        QString::fromUtf8(message.c_str()), QString());
         closeAllSatellites();
         pMainWindow_->quit();
         return;
      }

      // close all satellite windows if we are reloading
      bool reload = (pendingQuit == PendingQuitRestartAndReload);
      if (reload)
         closeAllSatellites();

      // launch next session
      Error error = launchNextSession(reload);
      if (error)
      {
         LOG_ERROR(error);

         showMessageBox(QMessageBox::Critical,
                        pMainWindow_,
                        desktop::activation().editionName(),
                        launchFailedErrorMessage(), QString());

         pMainWindow_->quit();
      }
   }
}

namespace {

core::WaitResult serverReady(const std::string& host, const std::string& port)
{
   core::http::Request request;
   request.setMethod("GET");
   request.setHost("host");
   request.setUri("/");
   request.setHeader("Accept", "*/*");
   request.setHeader("Connection", "close");

   core::http::Response response;
   Error error = core::http::sendRequest(host, port, request, &response);
   if (error)
      return WaitResult(WaitContinue, Success());
   else
      return WaitResult(WaitSuccess, Success());
}

} // end anonymous namespace

Error SessionLauncher::launchNextSession(bool reload)
{
   // unset the initial project environment variable it this doesn't
   // pollute future sessions
   core::system::unsetenv(kRStudioInitialProject);

   // disconnect the firstWorkbenchInitialized event so it doesn't occur
   // again when we launch the next session
   pMainWindow_->disconnect(SIGNAL(firstWorkbenchInitialized()));

   // delete the old process object
   pMainWindow_->setSessionProcess(nullptr);
   if (pRSessionProcess_)
   {
      delete pRSessionProcess_;
      pRSessionProcess_ = nullptr;
   }

   // build a new launch context -- re-use the same port if we aren't reloading
   QString port = !reload ? options().portNumber() : QString::fromUtf8("");
   QString host;
   QStringList argList;
   QUrl url;
   buildLaunchContext(&host, &port, &argList, &url);

   // launch the process
   Error error = launchSession(argList, &pRSessionProcess_);
   if (error)
     return error;

   // update the main window's reference to the process object
   pMainWindow_->setSessionProcess(pRSessionProcess_);

   // connect to quit event
   pMainWindow_->connect(pRSessionProcess_,
                         SIGNAL(finished(int,QProcess::ExitStatus)),
                         this, SLOT(onRSessionExited(int,QProcess::ExitStatus)));

   if (reload)
   {
      Error error = core::waitWithTimeout(
               boost::bind(serverReady, host.toStdString(), port.toStdString()),
               50,
               25,
               10);
      if (error)
         LOG_ERROR(error);

      nextSessionUrl_ = url;
      QTimer::singleShot(0, this, SLOT(onReloadFrameForNextSession()));
   }

   return Success();
}

void SessionLauncher::onReloadFrameForNextSession()
{
   pMainWindow_->loadUrl(nextSessionUrl_);
   nextSessionUrl_.clear();
}

void SessionLauncher::onLaunchFirstSession()
{
   Error error = launchFirstSession();
   if (error)
   {
      LOG_ERROR(error);
      activation().emitLaunchError(launchFailedErrorMessage());
   }
}

Error SessionLauncher::launchSession(const QStringList& argList,
                                     QProcess** ppRSessionProcess)
{
   // always remove the abend log path before launching
   Error error = abendLogPath().removeIfExists();
   if (error)
      LOG_ERROR(error);

   return  parent_process_monitor::wrapFork(
         boost::bind(launchProcess,
                     sessionPath_.absolutePath(),
                     argList,
                     ppRSessionProcess));
}

void SessionLauncher::onLaunchError(QString message)
{
   qApp->setQuitOnLastWindowClosed(true);
   if (!message.isEmpty())
   {
      QMessageBox errorMsg(safeMessageBoxIcon(QMessageBox::Critical),
                           desktop::activation().editionName(), message);
      errorMsg.addButton(QMessageBox::Close);
      errorMsg.setWindowFlag(Qt::WindowContextHelpButtonHint, false);
      errorMsg.exec();
   }
  qApp->exit(EXIT_FAILURE);
}

QString SessionLauncher::collectAbendLogMessage() const
{
   std::string contents;
   FilePath abendLog = abendLogPath();
   if (abendLog.exists())
   {
      Error error = core::readStringFromFile(abendLog, &contents);
      if (error)
         LOG_ERROR(error);

      error = abendLog.removeIfExists();
      if (error)
         LOG_ERROR(error);
   }

   return QString::fromStdString(contents);
}

QString SessionLauncher::launchFailedErrorMessage() const
{
   QString errMsg = QString::fromUtf8("The R session had a fatal error.");

   // check for abend log
   QString abendLogMessage = collectAbendLogMessage();

   // check for R version mismatch
   if (abendLogMessage.contains(
                    QString::fromUtf8("arguments passed to .Internal")))
   {
      errMsg.append(QString::fromUtf8("\n\nThis error was very likely caused "
                    "by R attempting to load packages from a different "
                    "incompatible version of R on your system. Please remove "
                    "other versions of R and/or remove environment variables "
                    "that reference libraries from other versions of R "
                    "before proceeding."));
   }

   if (!abendLogMessage.isEmpty())
      errMsg.append(QString::fromUtf8("\n\n").append(abendLogMessage));

   // check for stderr
   if (pRSessionProcess_)
   {
      QString errmsgs = QString::fromLocal8Bit(
                              pRSessionProcess_->readAllStandardError());
      if (errmsgs.size())
      {
         errMsg = errMsg.append(
                           QString::fromUtf8("\n\n")).append(errmsgs);
      }
   }

   return errMsg;
}

void SessionLauncher::buildLaunchContext(QString* pHost,
                                         QString* pPort,
                                         QStringList* pArgList,
                                         QUrl* pUrl) const
{
   *pHost = QString::fromUtf8("127.0.0.1");
   if (pPort->isEmpty())
      *pPort = desktop::options().newPortNumber();
   *pUrl = QUrl(QString::fromUtf8("http://") + *pHost +
                QString::fromUtf8(":") + *pPort + QString::fromUtf8("/"));

   if (!confPath_.empty())
   {
      *pArgList << QString::fromUtf8("--config-file") <<
                   QString::fromUtf8(confPath_.absolutePath().c_str());
   }
   else
   {
      // explicitly pass "none" so that rsession doesn't read an
      // /etc/rstudio/rsession.conf file which may be sitting around
      // from a previous configuration or install
      *pArgList << QString::fromUtf8("--config-file") <<
                   QString::fromUtf8("none");
   }

   *pArgList << QString::fromUtf8("--program-mode") <<
                QString::fromUtf8("desktop");

   *pArgList << QString::fromUtf8("--www-port") << *pPort;

   // create launch token if we haven't already
   if (s_launcherToken.empty())
      s_launcherToken = core::system::generateShortenedUuid();
   *pArgList << QString::fromUtf8("--launcher-token") <<
                QString::fromUtf8(s_launcherToken.c_str());

   if (options().runDiagnostics())
      *pArgList << QString::fromUtf8("--verify-installation") <<
                   QString::fromUtf8("1");
}

} // namespace desktop
} // namespace rstudio
<commit_msg>companion to https://github.com/rstudio/rstudio-pro/pull/971<commit_after>/*
 * DesktopSessionLauncher.cpp
 *
 * Copyright (C) 2009-19 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "DesktopSessionLauncher.hpp"

#include <iostream>

#include <boost/bind.hpp>

#include <core/http/Request.hpp>
#include <core/http/Response.hpp>
#include <core/http/TcpIpBlockingClient.hpp>

#include <core/WaitUtils.hpp>
#include <core/FileSerializer.hpp>
#include <core/system/Environment.hpp>
#include <core/system/ParentProcessMonitor.hpp>
#include <core/r_util/RUserData.hpp>

#include <QPushButton>

#include "DesktopOptions.hpp"
#include "DesktopSlotBinders.hpp"
#include "DesktopActivationOverlay.hpp"

#define RUN_DIAGNOSTICS_LOG(message) if (desktop::options().runDiagnostics()) \
             std::cout << (message) << std::endl;

using namespace rstudio::core;

namespace rstudio {
namespace desktop {

namespace {

std::string s_launcherToken;

void launchProcess(const std::string& absPath,
                   const QStringList& argList,
                   QProcess** ppProc)
{
   QProcess* process = new QProcess();
   process->setProgram(QString::fromStdString(absPath));
   process->setArguments(argList);
   if (options().runDiagnostics())
      process->setProcessChannelMode(QProcess::ForwardedChannels);

#ifdef _WIN32
   process->setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments* cpa)
   {
      cpa->flags |= CREATE_NEW_PROCESS_GROUP;
   });
#endif

   process->start();
   *ppProc = process;
}

FilePath abendLogPath()
{
   return desktop::userLogPath().complete("rsession_abort_msg.log");
}

void logEnvVar(const std::string& name)
{
   std::string value = core::system::getenv(name);
   if (!value.empty())
      RUN_DIAGNOSTICS_LOG("  " + name + "=" + value);
}

} // anonymous namespace

void SessionLauncher::launchFirstSession(const core::FilePath& installPath,
                                         bool devMode,
                                         const QStringList& arguments)
{
   connect(&activation(), &DesktopActivation::launchFirstSession,
           this, &SessionLauncher::onLaunchFirstSession, Qt::QueuedConnection);
   connect(&activation(), &DesktopActivation::launchError,
           this, &SessionLauncher::onLaunchError, Qt::QueuedConnection);
   activation().getInitialLicense(arguments, installPath, devMode);
}

Error SessionLauncher::launchFirstSession()
{
   // build a new new launch context
   QString host, port;
   QStringList argList;
   QUrl url;
   buildLaunchContext(&host, &port, &argList, &url);

   // show help home on first run
   argList.push_back(QString::fromUtf8("--show-help-home"));
   argList.push_back(QString::fromUtf8("1"));

   RUN_DIAGNOSTICS_LOG("\nAttempting to launch R session...");
   logEnvVar("RSTUDIO_WHICH_R");
   logEnvVar("R_HOME");
   logEnvVar("R_DOC_DIR");
   logEnvVar("R_INCLUDE_DIR");
   logEnvVar("R_SHARE_DIR");
   logEnvVar("R_LIBS");
   logEnvVar("R_LIBS_USER");
   logEnvVar("DYLD_LIBRARY_PATH");
   logEnvVar("LD_LIBRARY_PATH");
   logEnvVar("PATH");
   logEnvVar("HOME");
   logEnvVar("R_USER");

   // launch the process
   Error error = launchSession(argList, &pRSessionProcess_);
   if (error)
     return error;

   RUN_DIAGNOSTICS_LOG("\nR session launched, "
                           "attempting to connect on port "
                           + port.toStdString() +
                           "...");

   pMainWindow_ = new MainWindow(url);
   pMainWindow_->setAttribute(Qt::WA_DeleteOnClose);
   pMainWindow_->setSessionLauncher(this);
   pMainWindow_->setSessionProcess(pRSessionProcess_);
   pMainWindow_->setAppLauncher(pAppLaunch_);
   pAppLaunch_->setActivationWindow(pMainWindow_);

   desktop::options().restoreMainWindowBounds(pMainWindow_);

   RUN_DIAGNOSTICS_LOG("\nConnected to R session, attempting to initialize...\n");

   // one-time workbench initialized hook for startup file association
   if (!filename_.isNull() && !filename_.isEmpty())
   {
      StringSlotBinder* filenameBinder = new StringSlotBinder(filename_);
      pMainWindow_->connect(pMainWindow_,
                            SIGNAL(firstWorkbenchInitialized()),
                            filenameBinder,
                            SLOT(trigger()));
      pMainWindow_->connect(filenameBinder,
                            SIGNAL(triggered(QString)),
                            pMainWindow_,
                            SLOT(openFileInRStudio(QString)));
   }

   pMainWindow_->connect(pAppLaunch_,
                         SIGNAL(openFileRequest(QString)),
                         pMainWindow_,
                         SLOT(openFileInRStudio(QString)));

   pMainWindow_->connect(pRSessionProcess_,
                         SIGNAL(finished(int,QProcess::ExitStatus)),
                         this, SLOT(onRSessionExited(int,QProcess::ExitStatus)));

   pMainWindow_->connect(&activation(),
                         SIGNAL(licenseLost(QString)),
                         pMainWindow_,
                         SLOT(onLicenseLost(QString)));

   pMainWindow_->connect(&activation(), &DesktopActivation::updateLicenseWarningBar,
                         pMainWindow_, &MainWindow::onUpdateLicenseWarningBar);

   // show the window (but don't if we are doing a --run-diagnostics)
   if (!options().runDiagnostics())
   {
      finalPlatformInitialize(pMainWindow_);
      pMainWindow_->show();
      desktop::activation().setMainWindow(pMainWindow_);
      pAppLaunch_->activateWindow();
      pMainWindow_->loadUrl(url);
   }
   qApp->setQuitOnLastWindowClosed(true);
   return Success();
}

void SessionLauncher::closeAllSatellites()
{
   QWidgetList topLevels = QApplication::topLevelWidgets();
   for (auto pWindow : topLevels)
   {
      if (pWindow != pMainWindow_)
        pWindow->close();
   }
}

void SessionLauncher::onRSessionExited(int, QProcess::ExitStatus)
{
   // if this is a verify-installation session then just quit
   if (options().runDiagnostics())
   {
      pMainWindow_->quit();
      return;
   }

   int pendingQuit = pMainWindow_->collectPendingQuitRequest();

   // if there was no pending quit set then this is a crash
   if (pendingQuit == PendingQuitNone)
   {
      closeAllSatellites();
      pMainWindow_->webView()->webPage()->runJavaScript(
               QString::fromUtf8("window.desktopHooks.notifyRCrashed()"));

      if (abendLogPath().exists())
      {
         showMessageBox(QMessageBox::Critical,
                        pMainWindow_,
                        desktop::activation().editionName(),
                        launchFailedErrorMessage(), QString());
      }
   }

   // quit and exit means close the main window
   else if (pendingQuit == PendingQuitAndExit)
   {
      pMainWindow_->quit();
   }

   // otherwise this is a restart so we need to launch the next session
   else
   {
      if (!activation().allowProductUsage())
      {
         std::string message = "Unable to obtain a license. Please restart RStudio to try again.";
         std::string licenseMessage = activation().currentLicenseStateMessage();
         if (licenseMessage.empty())
            licenseMessage = "None Available";
         message += "\n\nDetails: ";
         message += licenseMessage;
         showMessageBox(QMessageBox::Critical,
                        pMainWindow_,
                        desktop::activation().editionName(),
                        QString::fromUtf8(message.c_str()), QString());
         closeAllSatellites();
         pMainWindow_->quit();
         return;
      }

      // close all satellite windows if we are reloading
      bool reload = (pendingQuit == PendingQuitRestartAndReload);
      if (reload)
         closeAllSatellites();

      // launch next session
      Error error = launchNextSession(reload);
      if (error)
      {
         LOG_ERROR(error);

         showMessageBox(QMessageBox::Critical,
                        pMainWindow_,
                        desktop::activation().editionName(),
                        launchFailedErrorMessage(), QString());

         pMainWindow_->quit();
      }
   }
}

namespace {

core::WaitResult serverReady(const std::string& host, const std::string& port)
{
   core::http::Request request;
   request.setMethod("GET");
   request.setHost("host");
   request.setUri("/");
   request.setHeader("Accept", "*/*");
   request.setHeader("Connection", "close");

   core::http::Response response;
   Error error = core::http::sendRequest(host, port, request, &response);
   if (error)
      return WaitResult(WaitContinue, Success());
   else
      return WaitResult(WaitSuccess, Success());
}

} // end anonymous namespace

Error SessionLauncher::launchNextSession(bool reload)
{
   // unset the initial project environment variable it this doesn't
   // pollute future sessions
   core::system::unsetenv(kRStudioInitialProject);

   // disconnect the firstWorkbenchInitialized event so it doesn't occur
   // again when we launch the next session
   pMainWindow_->disconnect(SIGNAL(firstWorkbenchInitialized()));

   // delete the old process object
   pMainWindow_->setSessionProcess(nullptr);
   if (pRSessionProcess_)
   {
      delete pRSessionProcess_;
      pRSessionProcess_ = nullptr;
   }

   // build a new launch context -- re-use the same port if we aren't reloading
   QString port = !reload ? options().portNumber() : QString::fromUtf8("");
   QString host;
   QStringList argList;
   QUrl url;
   buildLaunchContext(&host, &port, &argList, &url);

   // launch the process
   Error error = launchSession(argList, &pRSessionProcess_);
   if (error)
     return error;

   // update the main window's reference to the process object
   pMainWindow_->setSessionProcess(pRSessionProcess_);

   // connect to quit event
   pMainWindow_->connect(pRSessionProcess_,
                         SIGNAL(finished(int,QProcess::ExitStatus)),
                         this, SLOT(onRSessionExited(int,QProcess::ExitStatus)));

   if (reload)
   {
      Error error = core::waitWithTimeout(
               boost::bind(serverReady, host.toStdString(), port.toStdString()),
               50,
               25,
               10);
      if (error)
         LOG_ERROR(error);

      nextSessionUrl_ = url;
      QTimer::singleShot(0, this, SLOT(onReloadFrameForNextSession()));
   }

   return Success();
}

void SessionLauncher::onReloadFrameForNextSession()
{
   pMainWindow_->loadUrl(nextSessionUrl_);
   nextSessionUrl_.clear();
}

void SessionLauncher::onLaunchFirstSession()
{
   Error error = launchFirstSession();
   if (error)
   {
      LOG_ERROR(error);
      activation().emitLaunchError(launchFailedErrorMessage());
   }
}

Error SessionLauncher::launchSession(const QStringList& argList,
                                     QProcess** ppRSessionProcess)
{
   // always remove the abend log path before launching
   Error error = abendLogPath().removeIfExists();
   if (error)
      LOG_ERROR(error);

   return  parent_process_monitor::wrapFork(
         boost::bind(launchProcess,
                     sessionPath_.absolutePath(),
                     argList,
                     ppRSessionProcess));
}

void SessionLauncher::onLaunchError(QString message)
{
   qApp->setQuitOnLastWindowClosed(true);
   if (!message.isEmpty())
   {
      QMessageBox errorMsg(safeMessageBoxIcon(QMessageBox::Critical),
                           desktop::activation().editionName(), message);
      errorMsg.addButton(QMessageBox::Close);
      errorMsg.setWindowFlag(Qt::WindowContextHelpButtonHint, false);
      errorMsg.exec();
   }

   if (pMainWindow_)
      pMainWindow_->quit();
   else
      qApp->exit(EXIT_FAILURE);
}

QString SessionLauncher::collectAbendLogMessage() const
{
   std::string contents;
   FilePath abendLog = abendLogPath();
   if (abendLog.exists())
   {
      Error error = core::readStringFromFile(abendLog, &contents);
      if (error)
         LOG_ERROR(error);

      error = abendLog.removeIfExists();
      if (error)
         LOG_ERROR(error);
   }

   return QString::fromStdString(contents);
}

QString SessionLauncher::launchFailedErrorMessage() const
{
   QString errMsg = QString::fromUtf8("The R session had a fatal error.");

   // check for abend log
   QString abendLogMessage = collectAbendLogMessage();

   // check for R version mismatch
   if (abendLogMessage.contains(
                    QString::fromUtf8("arguments passed to .Internal")))
   {
      errMsg.append(QString::fromUtf8("\n\nThis error was very likely caused "
                    "by R attempting to load packages from a different "
                    "incompatible version of R on your system. Please remove "
                    "other versions of R and/or remove environment variables "
                    "that reference libraries from other versions of R "
                    "before proceeding."));
   }

   if (!abendLogMessage.isEmpty())
      errMsg.append(QString::fromUtf8("\n\n").append(abendLogMessage));

   // check for stderr
   if (pRSessionProcess_)
   {
      QString errmsgs = QString::fromLocal8Bit(
                              pRSessionProcess_->readAllStandardError());
      if (errmsgs.size())
      {
         errMsg = errMsg.append(
                           QString::fromUtf8("\n\n")).append(errmsgs);
      }
   }

   return errMsg;
}

void SessionLauncher::buildLaunchContext(QString* pHost,
                                         QString* pPort,
                                         QStringList* pArgList,
                                         QUrl* pUrl) const
{
   *pHost = QString::fromUtf8("127.0.0.1");
   if (pPort->isEmpty())
      *pPort = desktop::options().newPortNumber();
   *pUrl = QUrl(QString::fromUtf8("http://") + *pHost +
                QString::fromUtf8(":") + *pPort + QString::fromUtf8("/"));

   if (!confPath_.empty())
   {
      *pArgList << QString::fromUtf8("--config-file") <<
                   QString::fromUtf8(confPath_.absolutePath().c_str());
   }
   else
   {
      // explicitly pass "none" so that rsession doesn't read an
      // /etc/rstudio/rsession.conf file which may be sitting around
      // from a previous configuration or install
      *pArgList << QString::fromUtf8("--config-file") <<
                   QString::fromUtf8("none");
   }

   *pArgList << QString::fromUtf8("--program-mode") <<
                QString::fromUtf8("desktop");

   *pArgList << QString::fromUtf8("--www-port") << *pPort;

   // create launch token if we haven't already
   if (s_launcherToken.empty())
      s_launcherToken = core::system::generateShortenedUuid();
   *pArgList << QString::fromUtf8("--launcher-token") <<
                QString::fromUtf8(s_launcherToken.c_str());

   if (options().runDiagnostics())
      *pArgList << QString::fromUtf8("--verify-installation") <<
                   QString::fromUtf8("1");
}

} // namespace desktop
} // namespace rstudio
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012-2013 ARM Limited
 * All rights reserved
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * 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 holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Thomas Grass
 *          Andreas Hansson
 *          Sascha Bischoff
 */

#include <sstream>

#include "base/random.hh"
#include "cpu/testers/traffic_gen/traffic_gen.hh"
#include "debug/Checkpoint.hh"
#include "debug/TrafficGen.hh"
#include "sim/stats.hh"
#include "sim/system.hh"

using namespace std;

TrafficGen::TrafficGen(const TrafficGenParams* p)
    : MemObject(p),
      system(p->system),
      masterID(system->getMasterId(name())),
      configFile(p->config_file),
      elasticReq(p->elastic_req),
      nextTransitionTick(0),
      nextPacketTick(0),
      port(name() + ".port", *this),
      retryPkt(NULL),
      retryPktTick(0),
      updateEvent(this),
      drainManager(NULL)
{
}

TrafficGen*
TrafficGenParams::create()
{
    return new TrafficGen(this);
}

BaseMasterPort&
TrafficGen::getMasterPort(const string& if_name, PortID idx)
{
    if (if_name == "port") {
        return port;
    } else {
        return MemObject::getMasterPort(if_name, idx);
    }
}

void
TrafficGen::init()
{
    if (!port.isConnected())
        fatal("The port of %s is not connected!\n", name());

    // if the system is in timing mode active the request generator
    if (system->isTimingMode()) {
        DPRINTF(TrafficGen, "Timing mode, activating request generator\n");

        parseConfig();

        // enter initial state
        enterState(currState);
    } else {
        DPRINTF(TrafficGen,
                "Traffic generator is only active in timing mode\n");
    }
}

void
TrafficGen::initState()
{
    // when not restoring from a checkpoint, make sure we kick things off
    if (system->isTimingMode()) {
        // call nextPacketTick on the state to advance it
        nextPacketTick = states[currState]->nextPacketTick(elasticReq, 0);
        schedule(updateEvent, std::min(nextPacketTick, nextTransitionTick));
    } else {
        DPRINTF(TrafficGen,
                "Traffic generator is only active in timing mode\n");
    }
}

unsigned int
TrafficGen::drain(DrainManager *dm)
{
    if (retryPkt == NULL) {
        // shut things down
        nextPacketTick = MaxTick;
        nextTransitionTick = MaxTick;
        deschedule(updateEvent);
        return 0;
    } else {
        drainManager = dm;
        return 1;
    }
}

void
TrafficGen::serialize(ostream &os)
{
    DPRINTF(Checkpoint, "Serializing TrafficGen\n");

    // save ticks of the graph event if it is scheduled
    Tick nextEvent = updateEvent.scheduled() ? updateEvent.when() : 0;

    DPRINTF(TrafficGen, "Saving nextEvent=%llu\n", nextEvent);

    SERIALIZE_SCALAR(nextEvent);

    SERIALIZE_SCALAR(nextTransitionTick);

    SERIALIZE_SCALAR(nextPacketTick);

    SERIALIZE_SCALAR(currState);
}

void
TrafficGen::unserialize(Checkpoint* cp, const string& section)
{
    // restore scheduled events
    Tick nextEvent;
    UNSERIALIZE_SCALAR(nextEvent);
    if (nextEvent != 0) {
        schedule(updateEvent, nextEvent);
    }

    UNSERIALIZE_SCALAR(nextTransitionTick);

    UNSERIALIZE_SCALAR(nextPacketTick);

    // @todo In the case of a stateful generator state such as the
    // trace player we would also have to restore the position in the
    // trace playback and the tick offset
    UNSERIALIZE_SCALAR(currState);
}

void
TrafficGen::update()
{
    // if we have reached the time for the next state transition, then
    // perform the transition
    if (curTick() >= nextTransitionTick) {
        transition();
    } else {
        assert(curTick() >= nextPacketTick);
        // get the next packet and try to send it
        PacketPtr pkt = states[currState]->getNextPacket();
        numPackets++;
        if (!port.sendTimingReq(pkt)) {
            retryPkt = pkt;
            retryPktTick = curTick();
        }
    }

    // if we are waiting for a retry, do not schedule any further
    // events, in the case of a transition or a successful send, go
    // ahead and determine when the next update should take place
    if (retryPkt == NULL) {
        // schedule next update event based on either the next execute
        // tick or the next transition, which ever comes first
        nextPacketTick = states[currState]->nextPacketTick(elasticReq, 0);
        Tick nextEventTick = std::min(nextPacketTick, nextTransitionTick);
        DPRINTF(TrafficGen, "Next event scheduled at %lld\n", nextEventTick);
        schedule(updateEvent, nextEventTick);
    }
}

void
TrafficGen::parseConfig()
{
    // keep track of the transitions parsed to create the matrix when
    // done
    vector<Transition> transitions;

    // open input file
    ifstream infile;
    infile.open(configFile.c_str(), ifstream::in);
    if (!infile.is_open()) {
        fatal("Traffic generator %s config file not found at %s\n",
              name(), configFile);
    }

    // read line by line and determine the action based on the first
    // keyword
    string keyword;
    string line;

    while (getline(infile, line).good()) {
        // see if this line is a comment line, and if so skip it
        if (line.find('#') != 1) {
            // create an input stream for the tokenization
            istringstream is(line);

            // determine the keyword
            is >> keyword;

            if (keyword == "STATE") {
                // parse the behaviour of this state
                uint32_t id;
                Tick duration;
                string mode;

                is >> id >> duration >> mode;

                if (mode == "TRACE") {
                    string traceFile;
                    Addr addrOffset;

                    is >> traceFile >> addrOffset;

                    states[id] = new TraceGen(name(), masterID, duration,
                                              traceFile, addrOffset);
                    DPRINTF(TrafficGen, "State: %d TraceGen\n", id);
                } else if (mode == "IDLE") {
                    states[id] = new IdleGen(name(), masterID, duration);
                    DPRINTF(TrafficGen, "State: %d IdleGen\n", id);
                } else if (mode == "LINEAR" || mode == "RANDOM") {
                    uint32_t read_percent;
                    Addr start_addr;
                    Addr end_addr;
                    Addr blocksize;
                    Tick min_period;
                    Tick max_period;
                    Addr data_limit;

                    is >> read_percent >> start_addr >> end_addr >>
                        blocksize >> min_period >> max_period >> data_limit;

                    DPRINTF(TrafficGen, "%s, addr %x to %x, size %d,"
                            " period %d to %d, %d%% reads\n",
                            mode, start_addr, end_addr, blocksize, min_period,
                            max_period, read_percent);

                    if (blocksize > system->cacheLineSize())
                        fatal("TrafficGen %s block size (%d) is larger than "
                              "system block size (%d)\n", name(),
                              blocksize, system->cacheLineSize());

                    if (read_percent > 100)
                        fatal("%s cannot have more than 100% reads", name());

                    if (min_period > max_period)
                        fatal("%s cannot have min_period > max_period", name());

                    if (mode == "LINEAR") {
                        states[id] = new LinearGen(name(), masterID,
                                                   duration, start_addr,
                                                   end_addr, blocksize,
                                                   min_period, max_period,
                                                   read_percent, data_limit);
                        DPRINTF(TrafficGen, "State: %d LinearGen\n", id);
                    } else if (mode == "RANDOM") {
                        states[id] = new RandomGen(name(), masterID,
                                                   duration, start_addr,
                                                   end_addr, blocksize,
                                                   min_period, max_period,
                                                   read_percent, data_limit);
                        DPRINTF(TrafficGen, "State: %d RandomGen\n", id);
                    }
                } else {
                    fatal("%s: Unknown traffic generator mode: %s",
                          name(), mode);
                }
            } else if (keyword == "TRANSITION") {
                Transition transition;

                is >> transition.from >> transition.to >> transition.p;

                transitions.push_back(transition);

                DPRINTF(TrafficGen, "Transition: %d -> %d\n", transition.from,
                        transition.to);
            } else if (keyword == "INIT") {
                // set the initial state as the active state
                is >> currState;

                DPRINTF(TrafficGen, "Initial state: %d\n", currState);
            }
        }
    }

    // resize and populate state transition matrix
    transitionMatrix.resize(states.size());
    for (size_t i = 0; i < states.size(); i++) {
        transitionMatrix[i].resize(states.size());
    }

    for (vector<Transition>::iterator t = transitions.begin();
         t != transitions.end(); ++t) {
        transitionMatrix[t->from][t->to] = t->p;
    }

    // ensure the egress edges do not have a probability larger than
    // one
    for (size_t i = 0; i < states.size(); i++) {
        double sum = 0;
        for (size_t j = 0; j < states.size(); j++) {
            sum += transitionMatrix[i][j];
        }

        // avoid comparing floating point numbers
        if (abs(sum - 1.0) > 0.001)
            fatal("%s has transition probability != 1 for state %d\n",
                  name(), i);
    }

    // close input file
    infile.close();
}

void
TrafficGen::transition()
{
    // exit the current state
    states[currState]->exit();

    // determine next state
    double p = random_mt.gen_real1();
    assert(currState < transitionMatrix.size());
    double cumulative = 0.0;
    size_t i = 0;
    do {
        cumulative += transitionMatrix[currState][i];
        ++i;
    } while (cumulative < p && i < transitionMatrix[currState].size());

    enterState(i - 1);
}

void
TrafficGen::enterState(uint32_t newState)
{
    DPRINTF(TrafficGen, "Transition to state %d\n", newState);

    currState = newState;
    // we could have been delayed and not transitioned on the exact
    // tick when we were supposed to (due to back pressure when
    // sending a packet)
    nextTransitionTick = curTick() + states[currState]->duration;
    states[currState]->enter();
}

void
TrafficGen::recvRetry()
{
    assert(retryPkt != NULL);

    DPRINTF(TrafficGen, "Received retry\n");
    numRetries++;
    // attempt to send the packet, and if we are successful start up
    // the machinery again
    if (port.sendTimingReq(retryPkt)) {
        retryPkt = NULL;
        // remember how much delay was incurred due to back-pressure
        // when sending the request, we also use this to derive
        // the tick for the next packet
        Tick delay = curTick() - retryPktTick;
        retryPktTick = 0;
        retryTicks += delay;

        if (drainManager == NULL) {
            // packet is sent, so find out when the next one is due
            nextPacketTick = states[currState]->nextPacketTick(elasticReq,
                                                               delay);
            Tick nextEventTick = std::min(nextPacketTick, nextTransitionTick);
            schedule(updateEvent, std::max(curTick(), nextEventTick));
        } else {
            // shut things down
            nextPacketTick = MaxTick;
            nextTransitionTick = MaxTick;
            drainManager->signalDrainDone();
            // Clear the drain event once we're done with it.
            drainManager = NULL;
        }
    }
}

void
TrafficGen::regStats()
{
    // Initialise all the stats
    using namespace Stats;

    numPackets
        .name(name() + ".numPackets")
        .desc("Number of packets generated");

    numRetries
        .name(name() + ".numRetries")
        .desc("Number of retries");

    retryTicks
        .name(name() + ".retryTicks")
        .desc("Time spent waiting due to back-pressure (ticks)");
}

bool
TrafficGen::TrafficGenPort::recvTimingResp(PacketPtr pkt)
{
    delete pkt->req;
    delete pkt;

    return true;
}
<commit_msg>cpu: fix bug when TrafficGen deschedules event<commit_after>/*
 * Copyright (c) 2012-2013 ARM Limited
 * All rights reserved
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * 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 holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Thomas Grass
 *          Andreas Hansson
 *          Sascha Bischoff
 */

#include <sstream>

#include "base/random.hh"
#include "cpu/testers/traffic_gen/traffic_gen.hh"
#include "debug/Checkpoint.hh"
#include "debug/TrafficGen.hh"
#include "sim/stats.hh"
#include "sim/system.hh"

using namespace std;

TrafficGen::TrafficGen(const TrafficGenParams* p)
    : MemObject(p),
      system(p->system),
      masterID(system->getMasterId(name())),
      configFile(p->config_file),
      elasticReq(p->elastic_req),
      nextTransitionTick(0),
      nextPacketTick(0),
      port(name() + ".port", *this),
      retryPkt(NULL),
      retryPktTick(0),
      updateEvent(this),
      drainManager(NULL)
{
}

TrafficGen*
TrafficGenParams::create()
{
    return new TrafficGen(this);
}

BaseMasterPort&
TrafficGen::getMasterPort(const string& if_name, PortID idx)
{
    if (if_name == "port") {
        return port;
    } else {
        return MemObject::getMasterPort(if_name, idx);
    }
}

void
TrafficGen::init()
{
    if (!port.isConnected())
        fatal("The port of %s is not connected!\n", name());

    // if the system is in timing mode active the request generator
    if (system->isTimingMode()) {
        DPRINTF(TrafficGen, "Timing mode, activating request generator\n");

        parseConfig();

        // enter initial state
        enterState(currState);
    } else {
        DPRINTF(TrafficGen,
                "Traffic generator is only active in timing mode\n");
    }
}

void
TrafficGen::initState()
{
    // when not restoring from a checkpoint, make sure we kick things off
    if (system->isTimingMode()) {
        // call nextPacketTick on the state to advance it
        nextPacketTick = states[currState]->nextPacketTick(elasticReq, 0);
        schedule(updateEvent, std::min(nextPacketTick, nextTransitionTick));
    } else {
        DPRINTF(TrafficGen,
                "Traffic generator is only active in timing mode\n");
    }
}

unsigned int
TrafficGen::drain(DrainManager *dm)
{
    if (!updateEvent.scheduled()) {
        // no event has been scheduled yet (e.g. switched from atomic mode)
        return 0;
    }

    if (retryPkt == NULL) {
        // shut things down
        nextPacketTick = MaxTick;
        nextTransitionTick = MaxTick;
        deschedule(updateEvent);
        return 0;
    } else {
        drainManager = dm;
        return 1;
    }
}

void
TrafficGen::serialize(ostream &os)
{
    DPRINTF(Checkpoint, "Serializing TrafficGen\n");

    // save ticks of the graph event if it is scheduled
    Tick nextEvent = updateEvent.scheduled() ? updateEvent.when() : 0;

    DPRINTF(TrafficGen, "Saving nextEvent=%llu\n", nextEvent);

    SERIALIZE_SCALAR(nextEvent);

    SERIALIZE_SCALAR(nextTransitionTick);

    SERIALIZE_SCALAR(nextPacketTick);

    SERIALIZE_SCALAR(currState);
}

void
TrafficGen::unserialize(Checkpoint* cp, const string& section)
{
    // restore scheduled events
    Tick nextEvent;
    UNSERIALIZE_SCALAR(nextEvent);
    if (nextEvent != 0) {
        schedule(updateEvent, nextEvent);
    }

    UNSERIALIZE_SCALAR(nextTransitionTick);

    UNSERIALIZE_SCALAR(nextPacketTick);

    // @todo In the case of a stateful generator state such as the
    // trace player we would also have to restore the position in the
    // trace playback and the tick offset
    UNSERIALIZE_SCALAR(currState);
}

void
TrafficGen::update()
{
    // if we have reached the time for the next state transition, then
    // perform the transition
    if (curTick() >= nextTransitionTick) {
        transition();
    } else {
        assert(curTick() >= nextPacketTick);
        // get the next packet and try to send it
        PacketPtr pkt = states[currState]->getNextPacket();
        numPackets++;
        if (!port.sendTimingReq(pkt)) {
            retryPkt = pkt;
            retryPktTick = curTick();
        }
    }

    // if we are waiting for a retry, do not schedule any further
    // events, in the case of a transition or a successful send, go
    // ahead and determine when the next update should take place
    if (retryPkt == NULL) {
        // schedule next update event based on either the next execute
        // tick or the next transition, which ever comes first
        nextPacketTick = states[currState]->nextPacketTick(elasticReq, 0);
        Tick nextEventTick = std::min(nextPacketTick, nextTransitionTick);
        DPRINTF(TrafficGen, "Next event scheduled at %lld\n", nextEventTick);
        schedule(updateEvent, nextEventTick);
    }
}

void
TrafficGen::parseConfig()
{
    // keep track of the transitions parsed to create the matrix when
    // done
    vector<Transition> transitions;

    // open input file
    ifstream infile;
    infile.open(configFile.c_str(), ifstream::in);
    if (!infile.is_open()) {
        fatal("Traffic generator %s config file not found at %s\n",
              name(), configFile);
    }

    // read line by line and determine the action based on the first
    // keyword
    string keyword;
    string line;

    while (getline(infile, line).good()) {
        // see if this line is a comment line, and if so skip it
        if (line.find('#') != 1) {
            // create an input stream for the tokenization
            istringstream is(line);

            // determine the keyword
            is >> keyword;

            if (keyword == "STATE") {
                // parse the behaviour of this state
                uint32_t id;
                Tick duration;
                string mode;

                is >> id >> duration >> mode;

                if (mode == "TRACE") {
                    string traceFile;
                    Addr addrOffset;

                    is >> traceFile >> addrOffset;

                    states[id] = new TraceGen(name(), masterID, duration,
                                              traceFile, addrOffset);
                    DPRINTF(TrafficGen, "State: %d TraceGen\n", id);
                } else if (mode == "IDLE") {
                    states[id] = new IdleGen(name(), masterID, duration);
                    DPRINTF(TrafficGen, "State: %d IdleGen\n", id);
                } else if (mode == "LINEAR" || mode == "RANDOM") {
                    uint32_t read_percent;
                    Addr start_addr;
                    Addr end_addr;
                    Addr blocksize;
                    Tick min_period;
                    Tick max_period;
                    Addr data_limit;

                    is >> read_percent >> start_addr >> end_addr >>
                        blocksize >> min_period >> max_period >> data_limit;

                    DPRINTF(TrafficGen, "%s, addr %x to %x, size %d,"
                            " period %d to %d, %d%% reads\n",
                            mode, start_addr, end_addr, blocksize, min_period,
                            max_period, read_percent);

                    if (blocksize > system->cacheLineSize())
                        fatal("TrafficGen %s block size (%d) is larger than "
                              "system block size (%d)\n", name(),
                              blocksize, system->cacheLineSize());

                    if (read_percent > 100)
                        fatal("%s cannot have more than 100% reads", name());

                    if (min_period > max_period)
                        fatal("%s cannot have min_period > max_period", name());

                    if (mode == "LINEAR") {
                        states[id] = new LinearGen(name(), masterID,
                                                   duration, start_addr,
                                                   end_addr, blocksize,
                                                   min_period, max_period,
                                                   read_percent, data_limit);
                        DPRINTF(TrafficGen, "State: %d LinearGen\n", id);
                    } else if (mode == "RANDOM") {
                        states[id] = new RandomGen(name(), masterID,
                                                   duration, start_addr,
                                                   end_addr, blocksize,
                                                   min_period, max_period,
                                                   read_percent, data_limit);
                        DPRINTF(TrafficGen, "State: %d RandomGen\n", id);
                    }
                } else {
                    fatal("%s: Unknown traffic generator mode: %s",
                          name(), mode);
                }
            } else if (keyword == "TRANSITION") {
                Transition transition;

                is >> transition.from >> transition.to >> transition.p;

                transitions.push_back(transition);

                DPRINTF(TrafficGen, "Transition: %d -> %d\n", transition.from,
                        transition.to);
            } else if (keyword == "INIT") {
                // set the initial state as the active state
                is >> currState;

                DPRINTF(TrafficGen, "Initial state: %d\n", currState);
            }
        }
    }

    // resize and populate state transition matrix
    transitionMatrix.resize(states.size());
    for (size_t i = 0; i < states.size(); i++) {
        transitionMatrix[i].resize(states.size());
    }

    for (vector<Transition>::iterator t = transitions.begin();
         t != transitions.end(); ++t) {
        transitionMatrix[t->from][t->to] = t->p;
    }

    // ensure the egress edges do not have a probability larger than
    // one
    for (size_t i = 0; i < states.size(); i++) {
        double sum = 0;
        for (size_t j = 0; j < states.size(); j++) {
            sum += transitionMatrix[i][j];
        }

        // avoid comparing floating point numbers
        if (abs(sum - 1.0) > 0.001)
            fatal("%s has transition probability != 1 for state %d\n",
                  name(), i);
    }

    // close input file
    infile.close();
}

void
TrafficGen::transition()
{
    // exit the current state
    states[currState]->exit();

    // determine next state
    double p = random_mt.gen_real1();
    assert(currState < transitionMatrix.size());
    double cumulative = 0.0;
    size_t i = 0;
    do {
        cumulative += transitionMatrix[currState][i];
        ++i;
    } while (cumulative < p && i < transitionMatrix[currState].size());

    enterState(i - 1);
}

void
TrafficGen::enterState(uint32_t newState)
{
    DPRINTF(TrafficGen, "Transition to state %d\n", newState);

    currState = newState;
    // we could have been delayed and not transitioned on the exact
    // tick when we were supposed to (due to back pressure when
    // sending a packet)
    nextTransitionTick = curTick() + states[currState]->duration;
    states[currState]->enter();
}

void
TrafficGen::recvRetry()
{
    assert(retryPkt != NULL);

    DPRINTF(TrafficGen, "Received retry\n");
    numRetries++;
    // attempt to send the packet, and if we are successful start up
    // the machinery again
    if (port.sendTimingReq(retryPkt)) {
        retryPkt = NULL;
        // remember how much delay was incurred due to back-pressure
        // when sending the request, we also use this to derive
        // the tick for the next packet
        Tick delay = curTick() - retryPktTick;
        retryPktTick = 0;
        retryTicks += delay;

        if (drainManager == NULL) {
            // packet is sent, so find out when the next one is due
            nextPacketTick = states[currState]->nextPacketTick(elasticReq,
                                                               delay);
            Tick nextEventTick = std::min(nextPacketTick, nextTransitionTick);
            schedule(updateEvent, std::max(curTick(), nextEventTick));
        } else {
            // shut things down
            nextPacketTick = MaxTick;
            nextTransitionTick = MaxTick;
            drainManager->signalDrainDone();
            // Clear the drain event once we're done with it.
            drainManager = NULL;
        }
    }
}

void
TrafficGen::regStats()
{
    // Initialise all the stats
    using namespace Stats;

    numPackets
        .name(name() + ".numPackets")
        .desc("Number of packets generated");

    numRetries
        .name(name() + ".numRetries")
        .desc("Number of retries");

    retryTicks
        .name(name() + ".retryTicks")
        .desc("Time spent waiting due to back-pressure (ticks)");
}

bool
TrafficGen::TrafficGenPort::recvTimingResp(PacketPtr pkt)
{
    delete pkt->req;
    delete pkt;

    return true;
}
<|endoftext|>