Aaronius / hyper-cat

Turn your Hyper terminal into nyan cat while typing.
MIT License
211 stars 12 forks source link

No background on a clean Windows Server 2016 install #6

Closed JoshuaKGoldberg closed 7 years ago

JoshuaKGoldberg commented 7 years ago

No other plugins are installed.

image

// Future versions of Hyper may add additional config options,
// which will not automatically be merged into this file.
// See https://hyper.is#cfg for all currently supported options.

module.exports = {
  config: {
    // default font size in pixels for all tabs
    fontSize: 14,

    // font family with optional fallbacks
    fontFamily: 'Consolas, Menlo, "DejaVu Sans Mono", "Lucida Console", monospace',

    // terminal cursor background color and opacity (hex, rgb, hsl, hsv, hwb or cmyk)
    cursorColor: 'rgba(248,28,229,0.8)',

    // `BEAM` for |, `UNDERLINE` for _, `BLOCK` for █
    cursorShape: 'BEAM',

    // set to true for blinking cursor
    cursorBlink: true,

    // color of the text
    foregroundColor: '#fff',

    // terminal background color
    backgroundColor: '#000',

    // border color (window, tabs)
    borderColor: '#333',

    // custom css to embed in the main window
    css: '',

    // custom css to embed in the terminal window
    termCSS: '',

    // set to `true` (without backticks) if you're using a Linux setup that doesn't show native menus
    // default: `false` on Linux, `true` on Windows (ignored on macOS)
    showHamburgerMenu: '',

    // set to `false` if you want to hide the minimize, maximize and close buttons
    // additionally, set to `'left'` if you want them on the left, like in Ubuntu
    // default: `true` on windows and Linux (ignored on macOS)
    showWindowControls: '',

    // custom padding (css format, i.e.: `top right bottom left`)
    padding: '12px 14px',

    // the full list. if you're going to provide the full color palette,
    // including the 6 x 6 color cubes and the grayscale map, just provide
    // an array here instead of a color map object
    colors: {
      black: '#000000',
      red: '#ff0000',
      green: '#33ff00',
      yellow: '#ffff00',
      blue: '#0066ff',
      magenta: '#cc00ff',
      cyan: '#00ffff',
      white: '#d0d0d0',
      lightBlack: '#808080',
      lightRed: '#ff0000',
      lightGreen: '#33ff00',
      lightYellow: '#ffff00',
      lightBlue: '#0066ff',
      lightMagenta: '#cc00ff',
      lightCyan: '#00ffff',
      lightWhite: '#ffffff'
    },

    // the shell to run when spawning a new session (i.e. /usr/local/bin/fish)
    // if left empty, your system's login shell will be used by default
    // make sure to use a full path if the binary name doesn't work
    // (e.g `C:\\Windows\\System32\\bash.exe` instead of just `bash.exe`)
    // if you're using powershell, make sure to remove the `--login` below
    shell: '',

    // for setting shell arguments (i.e. for using interactive shellArgs: ['-i'])
    // by default ['--login'] will be used
    shellArgs: ['--login'],

    // for environment variables
    env: {},

    // set to false for no bell
    bell: 'SOUND',

    // if true, selected text will automatically be copied to the clipboard
    copyOnSelect: false

    // if true, on right click selected text will be copied or pasted if no
    // selection is present (true by default on Windows)
    // quickEdit: true

    // URL to custom bell
    // bellSoundURL: 'http://example.com/bell.mp3',

    // for advanced config flags please refer to https://hyper.is/#cfg
  },

  // a list of plugins to fetch and install from npm
  // format: [@org/]project[#version]
  // examples:
  //   `hyperpower`
  //   `@company/project`
  //   `project#1.0.1`
  plugins: ["hyper-cat"],

  // in development, you can create a directory under
  // `~/.hyper_plugins/local/` and include it here
  // to load it and avoid it being `npm install`ed
  localPlugins: []
};
Magneticmagnum commented 7 years ago

Same issue here, on Windows 7, hyper version 1.3.1

towicode commented 7 years ago

Same here windows 10. Seems to be a number of issues for win. @Aaronius do you have any plan on fixing this? I might try looking into it.

Aaronius commented 7 years ago

Unfortunately, I don't plan on fixing it. Go ahead and take a shot.

sjess commented 7 years ago

Here my fix for the Scroller and Background issue for windows. Put the plugin into a local plugins folder with this index.js.

const Color = require('color');
const path = require('path');
var css_path = path.join(__dirname, 'stars.jpg');

css_path = css_path.replace(/\\/g, "/");

const RAINBOW_ALPHA_DECAY = 0.95;
const RAINBOW_COLORS = [
  '#fe0000',
  '#ffa500',
  '#ffff00',
  '#00fb00',
  '#009eff',
  '#6531ff'
].map(color => Color(color).rgb());

// Cat colors
const BLACK = '#000000';
const BEIGE = '#f9d28f';
const PINK = '#fe91fe';
const DEEPPINK = '#f90297';
const GRAY = '#9d9d9d';
const SALMON = '#ff9593';

const STAGGER_HEIGHT = 2;

const ACTIVE_DURATION = 250;

exports.decorateConfig = config => {
  return Object.assign({}, config, {
    cursorShape: 'block',
    termCSS: `
      ${config.termCSS || ''}
      .cursor-node.hypercat-active {
        opacity: 0 !important;
      };
    `,
    css: `
      ${config.css || ''}
      .hypercat-overlay {
        overflow: hidden;
        display: none;
        height: 100%;
      }

      canvas {
        display: block;
        height: 100%;
        overflow: hidden;
      }

      .hypercat-overlay.hypercat-active {
        display: block;
        background-image: url(file://${css_path});
        background-repeat: repeat;
        -webkit-animation: starscroll 4s infinite linear
      }

      @-webkit-keyframes starscroll {
        from {background-position:0 0;}
        to {background-position:-1600px 0;}
      }

      .hypercat-cursor {
        position: absolute;
        pointerEvents: none;
        background: radial-gradient(circle, ${DEEPPINK} 10%, transparent 10%),
          radial-gradient(circle, ${DEEPPINK} 10%, ${PINK} 10%) 3px 3px;
        backgroundSize: 6px 6px;
        borderWidth: 1px;
        borderColor: black;
        borderStyle: solid;
      }
      .hypercat-asset {
        display: none;
        position: absolute;
        pointerEvents: none;
      }
    `
  })
};

// Share audio across terminal instances.
let audio;
let audioTimeout;
let audioEnabled;
const playAudio = () => {
  if (!audioEnabled) {
    return;
  }

  clearTimeout(audioTimeout);
  audio.play();
  audioTimeout = setTimeout(audio.pause.bind(audio), ACTIVE_DURATION);
};

exports.middleware = (store) => (next) => (action) => {
  if ('SESSION_ADD_DATA' === action.type) {
    const { data } = action;
    if (/(hyper-cat-toggle-audio: command not found)|(command not found: hyper-cat-toggle-audio)/.test(data)) {
      audioEnabled = !audioEnabled;
      global.localStorage.setItem('hyperCatAudioEnabled', String(audioEnabled));
    } else {
      next(action);
    }
  } else {
    next(action);
  }
};

// code based on
// https://atom.io/packages/power-mode
// https://github.com/itszero/rage-power/blob/master/index.jsx
exports.decorateTerm = (Term, { React, notify }) => {
  // localStorage isn't immediately available when hyper starts up. That is why this statement
  // is in here. It's not a very appropriate place for it I would guess, but YOLO. Nyan on.
  audioEnabled = global.localStorage.getItem('hyperCatAudioEnabled') !== 'false';

  return class extends React.Component {
    constructor (props, context) {
      super(props, context);
      this._drawFrame = this._drawFrame.bind(this);
      this._resizeCanvas = this._resizeCanvas.bind(this);
      this._onTerminal = this._onTerminal.bind(this);
      this._onCursorChange = this._onCursorChange.bind(this);
      this._rainbows = [];
    }

    _onTerminal (term) {
      if (this.props.onTerminal) this.props.onTerminal(term);
      this._termDiv = term.div_;
      this._termCursor = term.cursorNode_;
      this._termWindow = term.document_.defaultView;
      this._termScreen = term.document_.querySelector('x-screen');
      this._observer = new MutationObserver(this._onCursorChange);
      this._observer.observe(this._termCursor, {
        attributes: true,
        childList: false,
        characterData: false
      });

      this._initAudio();
      this._initOverlay();
    }

    _initAudio() {
      if (audio) {
        return;
      }

      audio = document.createElement('audio');
      audio.id = 'audio-player';
      audio.src = path.join(__dirname, 'nyan.mp3');
      audio.type = 'audio/mpeg';
      document.body.appendChild(audio);
    }

    _initOverlay() {
      this._overlay = document.createElement('div');
      this._overlay.classList.add('hypercat-overlay');
      document.body.appendChild(this._overlay);

      this._canvas = document.createElement('canvas');
      this._canvasContext = this._canvas.getContext('2d');
      this._canvas.width = window.innerWidth;
      this._canvas.height = window.innerHeight;
      this._overlay.appendChild(this._canvas);

      this._termWindow.requestAnimationFrame(this._drawFrame);
      this._termWindow.addEventListener('resize', this._resizeCanvas);

      this._initCatCursor();
      this._initCatAssets();
    }

    _createCatAsset(filename) {
      const img = new Image();   // Create new img element
      img.src = path.join(__dirname, filename);
      img.classList.add('hypercat-asset');
      this._overlay.appendChild(img);
      return img;
    }

    _initCatAssets() {
      this._catLegs = this._createCatAsset('legs.svg');
      this._catHead = this._createCatAsset('head.svg');
      this._catTail = this._createCatAsset('tail.svg');
    }

    _initCatCursor() {
      const catCursor = document.createElement('div');
      catCursor.classList.add('hypercat-cursor');

      this._overlay.appendChild(catCursor);
      this._catCursor = catCursor;
    }

    _resizeCanvas() {
      this._canvas.width = window.innerWidth;
      this._canvas.height = window.innerHeight;
    }

    _drawRainbow(ctx, rainbow, staggerUp) {
      const stripeHeight = rainbow.height / RAINBOW_COLORS.length;

      RAINBOW_COLORS.forEach((color, i) => {
        ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${rainbow.alpha})`;
        ctx.fillRect(
          rainbow.left,
          rainbow.top + stripeHeight * i + (staggerUp ? -STAGGER_HEIGHT : STAGGER_HEIGHT),
          rainbow.width,
          stripeHeight
        );
      });
    }

    _drawFrame() {
      this._canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height);

      let staggerUp = !this._isStaggeredUp;

      for (var i = this._rainbows.length - 1; i >= 0; i--) {
        const rainbow = this._rainbows[i];
        this._drawRainbow(this._canvasContext, rainbow, staggerUp);

        rainbow.alpha *= RAINBOW_ALPHA_DECAY;

        if (rainbow.alpha < 0.1) {
          this._rainbows.splice(i, 1);
        }

        staggerUp = !staggerUp;
      }

      this._termWindow.requestAnimationFrame(this._drawFrame);
    }

    _spawnRainbow(rect) {
      // Make the rainbow a bit shorter than the cat for a proper nyan.
      this._rainbows.push(Object.assign({ alpha: 1 }, {
        left: rect.left,
        top: rect.top + rect.height * .1,
        width: rect.width,
        height: rect.height * .80
      }));
    }

    _onCursorChange() {
      const origin = this._termDiv.getBoundingClientRect();
      const cursorRect = this._termCursor.getBoundingClientRect();

      const left = origin.left + cursorRect.left;
      const top = origin.top + cursorRect.top;
      const width = cursorRect.width;
      const height = cursorRect.height;

      if (this._prevCursorRect &&
        this._prevCursorRect.left === left &&
        this._prevCursorRect.top === top &&
        this._prevCursorRect.width === width &&
        this._prevCursorRect.height === height) {
        return;
      }

      this.setActive(true);

      this._isStaggeredUp = !this._isStaggeredUp;

      const staggerTop = top + (this._isStaggeredUp ? -STAGGER_HEIGHT : STAGGER_HEIGHT);

      Object.assign(this._catCursor.style, {
        left: left + 'px',
        top: staggerTop + 'px',
        width: width + 'px',
        height: height + 'px'
      });

      if (this._catHead.complete && this._catLegs.complete && this._catTail.complete) {
        const scale = width / this._catHead.naturalWidth;

        Object.assign(this._catHead.style, {
          display: 'block',
          width: this._catHead.naturalWidth * scale + 'px',
          height: this._catHead.naturalHeight * scale + 'px',
          left: left + width - this._catHead.width * .75 + 'px',
          // Bottom of the head should align with the bottom of the cursor.
          // There are basically 15 rows of blocks, 2 of which extend below the head.
          // These 2 rows of blocks contain the front legs.
          top: staggerTop + height - this._catHead.height * (13 / 15) + 'px'
        });

        Object.assign(this._catLegs.style, {
          display: 'block',
          width: this._catLegs.naturalWidth * scale + 'px',
          height: this._catLegs.naturalHeight * scale + 'px',
          left: left - this._catLegs.width * (2 / 10) + 'px',
          top: staggerTop + height - this._catLegs.height * (2 / 4) + 'px'
        });

        Object.assign(this._catTail.style, {
          display: 'block',
          width: this._catTail.naturalWidth * scale + 'px',
          height: this._catTail.naturalHeight * scale + 'px',
          left: left - this._catTail.width + 'px',
          top: staggerTop + height - this._catTail.height * (11 / 7) + 'px'
        });
      }

      if (this._prevCursorRect) {
        this._spawnRainbow(this._prevCursorRect);
      }

      this._prevCursorRect = {
        left,
        top,
        width,
        height
      };
    }

    setActive(active) {
      this._overlay.classList.toggle('hypercat-active', active);
      this._termCursor.classList.toggle('hypercat-active', active);

      if (active) {
        this._termScreen.style.color = 'white';
        playAudio();

        clearTimeout(this._activeTimeout);
        this._activeTimeout = setTimeout(() => {
          this.setActive(false);
        }, ACTIVE_DURATION)
      } else {
        this._termScreen.style.color = this.props.term.foregroundColor_;
      }
    }

    componentWillReceiveProps(nextProps) {
      if (!nextProps.isTermActive) {
        this.setActive(false);
      }
    }

    render() {
      return React.createElement(Term, Object.assign({}, this.props, {
        onTerminal: this._onTerminal
      }));
    }

    componentWillUnmount() {
      document.body.removeChild(this._overlay);

      if (this._observer) {
        this._observer.disconnect();
      }
    }
  }
};
Aaronius commented 7 years ago

Thanks @sjess! I've incorporated your fix and released it as 1.0.1. I didn't test it personally on Windows--only that it didn't break on Mac.