2013年11月22日金曜日

Slateを使うとマウスが必要無くなるから窓から放り投げよう


やあ、みなさん。

SlateというMac専用のオープンソースのウィンドウマネージャを使うと、キーボード生活が楽になります。
jigish/slate - GitHub

Macにおけるウィンドウマネージャ的な役割を担うソフトウェアで有名なものには『BetterTouchTool』や『Spectacle』なんかがあるかと思いますが、それらよりかはかなり細かく設定できると思われて便利です。

設定方法

ホームフォルダに".slate.js"というJavaScriptファイルを置いとくと、それを設定ファイルとして読み込んでくれます。
専用の表記方法を利用した".slate"というファイルも置いておけるらしくて、これらはショートカットキーが被ってなければ共存できるらしいです。

何ができるの?

この記事の最後に僕が使っている".slate.js"のソースを載せますが、それを使うと以下のようなことができます。

alt-shift-tab 複数ウィンドウあるアプリを画面にタイル状に並べる
alt-cmd-t iTermにフォーカスor起動
alt-cmd-e Emacs〃
alt-cmd-b Firefox〃
alt-cmd-f Finder〃
alt-cmd-d Dictionary〃
alt-cmd-i iTunes〃
alt-cmd-space Found〃
alt-cmd-p Preview〃
alt-cmd-m Mjograph〃
alt-h, j, k or l Vimライクにウィンドウのフォーカスを移動
alt-i 下に隠れているウィンドウをフォーカス
alt-p スクリーン間でフォーカスを移動
alt-shift-p 次のスクリーンへ飛ばす
alt-shift-o 四隅に飛ばす
alt-shift-u 左右に飛ばす
alt-shift-i 上下に飛ばす
alt-shift-h ウィンドウが左にあるなら縮小, 右にあるなら拡大
alt-shift-l ウィンドウが右にあるなら縮小, 左にあるなら拡大
alt-shift-k ウィンドウが上にあるなら縮小, 下にあるなら拡大
alt-shift-j ウィンドウが下にあるなら縮小, 上にあるなら拡大
alt-shift-m ウィンドウを最大化
alt-n 同じアプリケーションで別のウィンドウにフォーカスする

alt-cmd系はアプリを起動するものに割り当てて、alt-shift系はウィンドウのリサイズ関係、alt系はウィンドウのフォーカス関係になってます。
alt-ctrlは僕のキーボード配列じゃ打ちづらいんで入れてませんが、入れてもいいと思います。

設定

以下が僕の".slate.js"ファイルです。
JavaScript分かんないんで、ほとんどコピペです。
// http://yohasebe.com/wp/archives/3513
// for tiling windows of focused app onto desktop
// (2 x 2, clockwise)
 
var topLeft = slate.operation("corner", {
  "direction" : "top-left",
  "width"  : "screenSizeX/2",
  "height" : "screenSizeY/2"
});
 
var topRight = slate.operation("corner", {
  "direction" : "top-right",
  "width"  : "screenSizeX/2",
  "height" : "screenSizeY/2"
});
 
var bottomRight = slate.operation("corner", {
  "direction" : "bottom-right",
  "width"  : "screenSizeX/2",
  "height" : "screenSizeY/2"
});
 
var bottomLeft = slate.operation("corner", {
  "direction" : "bottom-left",
  "width"  : "screenSizeX/2",
  "height" : "screenSizeY/2"
});

// [tab]+alt+shiftでアプリのウィンドウをタイル状に並べる
var tileKey = "tab:alt;shift";
 
slate.bind(tileKey, function(win){
  var appName = win.app().name();    
  var tiled = {};
  tiled[appName] = {
    "operations" : [topLeft, topRight, bottomRight, bottomLeft],
    "main-first" : true,
    "repeat"     : true
  };      
  var tiledLayout = slate.layout("tiledLayout", tiled);
  slate.operation("layout", {"name" : tiledLayout }).run();
  slate.operation("show", {"app" : appName}).run();
});

// http://d.hatena.ne.jp/sugyan/20130301/1362129310
// アプリ立ち上げる関数
var launch_and_focus = function (target) {
    return function (win) {
        var apps = [];
        S.eachApp(function (app) { apps.push(app.name()); });
        if (! _.find(apps, function (name) { return name === target; })) {
            win.doOperation(
                S.operation('shell', {
                    command: "/usr/bin/open -a " + target,
                    waithFoeExit: true
                })
            );
        }
        win.doOperation(S.operation('focus', { app: target }));
    };
};
S.bind('t:alt,cmd', launch_and_focus('iTerm'));
S.bind('e:alt,cmd', launch_and_focus('Emacs'));
S.bind('b:alt,cmd', launch_and_focus('Firefox'));
S.bind('f:alt,cmd', launch_and_focus('Finder'));
S.bind('d:alt,cmd', launch_and_focus('Dictionary'));
S.bind('i:alt,cmd', launch_and_focus('iTunes'));
S.bind('space:alt,cmd', launch_and_focus('Found'));
S.bind('p:alt,cmd', launch_and_focus('Preview'));
S.bind('m:alt,cmd', launch_and_focus('Mjograph'));

// http://www.infiniteloop.co.jp/blog/2013/08/osx_slate/
var util = {
  // alt + ..
  key: function(k, mod) {
    return k + ':alt' + (mod ? ',' + mod : '');
  },
  focusWindow: function(f) {
    var hit = false;
    slate.eachApp(function(app) {
      if (hit) return;
      app.eachWindow(function(win) {
        if (hit) return;
        if (f(win)) {
          win.focus();
          hit = true;
        }
      });
    });
  },
  nextScreen: function(screen) {
    return slate.screenForRef(String( (screen.id()+1)%slate.screenCount() ));
  }
};
 
// ----------- 以下 alt+

// hjkl       .. その方向へフォーカス移動
slate.bind(util.key('h'), slate.operation('focus', { direction: 'left' }));
slate.bind(util.key('j'), slate.operation('focus', { direction: 'down' }));
slate.bind(util.key('k'), slate.operation('focus', { direction: 'up' }));
slate.bind(util.key('l'), slate.operation('focus', { direction: 'right' }));
 
// i          .. 下に隠れているウィンドウをフォーカス
slate.bind(util.key('i'), slate.operation('focus', { direction: 'behind' }));

 
// p          .. スクリーン間でフォーカスを移動
slate.bind(util.key('p'), function(win) {
  var next = util.nextScreen(slate.screen());
 
  util.focusWindow(function(win) {
    return win.screen().id() == next.id();
  });
});

// p+shift    .. 次のスクリーンへ飛ばす
slate.bind(util.key('p', 'shift'), function(win) {
  if (!win) return;
  var next = util.nextScreen(win.screen());
 
  win.move(next.visibleRect());
});
 
// o+shift    .. 4隅に飛ばす
var corners = slate.bind(util.key('o', 'shift'), slate.operation('chain', {
  operations: _.map(['top-right', 'bottom-right', 'bottom-left', 'top-left'], function(d) {
    return slate.operation('corner', {
      direction: d,
      width: 'screenSizeX/2',
      height: 'screenSizeY/2'
    });
  })
}));
 
// u+shift    .. 左右に飛ばす
slate.bind(util.key('u', 'shift'), slate.operation('chain', {
  operations: _.map(['left', 'right'], function(d) {
    return slate.operation('push', {
      direction: d,
      style: 'bar-resize:screenSizeX/2'
    });
  })
}));

// i+shift    .. 上下に飛ばす
slate.bind(util.key('i', 'shift'), slate.operation('chain', {
  operations: _.map(['top', 'bottom'], function(d) {
    return slate.operation('push', {
      direction: d,
      style: 'bar-resize:screenSizeY/2'
    });
  })
}));

 
// h+shift   .. ウィンドウが左にあるなら縮小, 右にあるなら拡大
slate.bind(util.key('h', 'shift'), function(win) {
  if (!win) return;
  var rect = win.rect();
  var bounds = win.screen().visibleRect();
  if (bounds.x + bounds.width - 30 < rect.x + rect.width) {
    rect.x -= bounds.width * 0.05;
    rect.width = bounds.x + bounds.width - rect.x;
  } else {
    rect.width -= bounds.width * 0.05;
  }
  win.doOperation('move', rect);
});
 
// l+shift   .. ウィンドウが右にあるなら縮小, 左にあるなら拡大
slate.bind(util.key('l', 'shift'), function(win) {
  if (!win) return;
  var rect = win.rect();
  var bounds = win.screen().visibleRect();
  if (rect.x < bounds.x + 30) {
    rect.x = bounds.x;
    rect.width += bounds.width * 0.05;
  } else {
    rect.x += bounds.width * 0.05;
    rect.width -= bounds.width * 0.05;
  }
  win.doOperation('move', rect);
});

// k+shift   .. ウィンドウが上にあるなら縮小, 下にあるなら拡大
slate.bind(util.key('k', 'shift'), function(win) {
  if (!win) return;
  var rect = win.rect();
  var bounds = win.screen().visibleRect();
  if (bounds.y + bounds.height - 30 < rect.y + rect.height) {
    rect.y -= bounds.height * 0.05;
    rect.height += bounds.height * 0.05;
  } else {
    rect.height -= bounds.height * 0.05;
  }
  win.doOperation('move', rect);
});

// j+shift   .. ウィンドウが下にあるなら縮小, 上にあるなら拡大
slate.bind(util.key('j', 'shift'), function(win) {
  if (!win) return;
  var rect = win.rect();
  var bounds = win.screen().visibleRect();
  if (rect.y < bounds.y + 30) {
    rect.y = bounds.y;
    rect.height += bounds.height * 0.05;
  } else {
    rect.y += bounds.height * 0.05;
    rect.height -= bounds.height * 0.05;
  }
  win.doOperation('move', rect);
});

 
// m          .. 最大化
slate.bind(util.key('m', 'shift'), function(win) {
  if (!win) return;
  var bounds = win.screen().visibleRect();
  win.doOperation('move', bounds);
});


// http://mint.hateblo.jp/category/Slate
// 同じアプリケーションで別のウィンドウにフォーカスする (Chrome対応版)
S.bind('n:alt', function() {
function get_next_win(windows) {
truth_values_of_is_main = _.map(windows, function(w){ return w.isMain(); })
next_idx = _.indexOf(truth_values_of_is_main, 1) + 1;
if (next_idx >= _.size(windows)) { return windows[0]; }
return windows[next_idx];
}
windows = [];
slate.app().eachWindow(function(win){
if (win.title() !== '') { windows.push(win); } // タイトルが無いウィンドウは無視
});
if (_.size(windows) === 1){ return; }
sorted = _.sortBy(windows, function(win){ return win.title(); });
get_next_win(sorted).focus();
});
設定し終わったらマウスを外に投げましょう。

追記

ウィンドウの微妙な位置調整がやりたい場面があって、その関数がまだ無かったので作りました。
Alt + Shift + WASDキーで上下左右の移動ができるようになります。
// d+shift   .. ウィンドウを右に移動
slate.bind(util.key('d', 'shift'), function(win) {
    if (!win) return;
    var rect = win.rect();
    var bounds = win.screen().visibleRect();
    rect.x += bounds.width * 0.05;
    win.doOperation('move', rect);
});

// a+shift   .. ウィンドウを左に移動
slate.bind(util.key('a', 'shift'), function(win) {
    if (!win) return;
    var rect = win.rect();
    var bounds = win.screen().visibleRect();
    rect.x -= bounds.width * 0.05;
    win.doOperation('move', rect);
});

// s+shift   .. ウィンドウを下に移動
slate.bind(util.key('s', 'shift'), function(win) {
    if (!win) return;
    var rect = win.rect();
    var bounds = win.screen().visibleRect();
    rect.y += bounds.height * 0.05;
    win.doOperation('move', rect);
});

// w+shift   .. ウィンドウを上に移動
slate.bind(util.key('w', 'shift'), function(win) {
    if (!win) return;
    var rect = win.rect();
    var bounds = win.screen().visibleRect();
    rect.y -= bounds.height * 0.05;
    win.doOperation('move', rect);
});



参考サイト

Slateを入れてみた - すぎゃーんメモ
Slate - ミントフレーバー緑茶
Slateの設定ファイル(JavaScript版) | yohasebe.com
OS Xで作業効率を5%上げるSlateの紹介 | 株式会社インフィニットループ技術ブログ

0 件のコメント :

コメントを投稿