もちっとメモ

もちっとメモ

もぐりのエンジニアが日々の中で試してみたことを気が向いたときに書き連ねていきます

技術書典14に参加しました

お久しぶりです。 このブログも実に2年ぶりの更新になるようです。 いったいどんだけサボってるんだ...

さて、6/4(日)に技術書典14が終了してはや1週間がたちました。 無事終われてよかったです。

techbookfest.org

技術書典(ぎじゅつしょてん)は新しい技術に出会えるお祭りです。 技術書典は「いろんな技術の普及を手伝いたい」という想いではじまりました。技術書を中心として出展者はノウハウを詰め込み、来場者はこの場にしかないおもしろい技術書をさがし求める、そんな技術に関わる人のための場として我々は技術書典を開催しています。技術書典公式サイトより

弊サークルも参加しており、これで累計3回目の参加になりました。

ご購入いただいた方、またオフラインでスペースにお立ち寄りいただいた方、ありがとうございました。

今年のオフラインの来場者数は2100人だったようです。

コロナ前に比べるとさすがに劣りますが、着実に賑わいを取り戻しているようでうれしいです。

参加者数の推移~技術書典スポンサー資料より https://techbookfest.org/assets/tbf14/for-sponsors.pdf

やっぱり、直接お会いしてお気に入りの技術に関して話せるというのは楽しいですよね!

弊サークルでは、今回は既刊に加えて2冊の新刊を頒布しました。

お品書き

techbookfest.org

techbookfest.org

その他既刊についてはサークルページをご覧くださいませ。 techbookfest.org

執筆の裏話

今回は新刊2冊出しており、1冊は私著でもう1冊は友人に書いてもらいました。

PySpark本は、友人のTくんに書いてもらいました。 難易度的にもページ数的にも読みやすい1冊になっています。 また一人技術同人沼に引き込んでしまいましたが、執筆以外の全面サポートしたおかげか、また次も参加したいと言ってくれたのでよかったです。

そして、私が執筆したのはもう1冊のSolr本です。 ひと言で言うと、全文検索エンジンであるSolrと使って、今はやりのベクトル検索をやろうという本です。

そのページ数は、なんと驚異の318ページです。 なるべく多くの人に手に取ってもらいたいなと思い、書きたいことを全部詰め込んだ結果、こんなになってしまいました。 アホみたいなページ数ですよね。 出来上がりを見て自分でもびっくりしました。 こんなにも頭の悪いページ数のレビューをしてくれたSさんありがとう。 会場で直接お話ししたみなさんが口をそろえて、「分厚!」と言っていたのでトチ狂ったページ数であることは間違いなさそうです。

その甲斐あってか、思いのほかいろいろな方に手に取っていただけたようで嬉しかったです。 「はじめに」にも書いた通り、ある程度検索エンジン特にSolrの運用経験のある人をターゲットにしていたのですが、そうでない方にも興味を持っていただけました。

実は、分厚すぎるということで、急きょ当初のターゲット層に絞ったコンパクト版も用意しておりました。

techbookfest.org

今回は3か月前から執筆にとりかかったのですが、えらい時間がかかりました。

ボリュームを考えたらそりゃそうだとと言われそうですが、実は内容量もさることながら、地味に表紙にも時間と手間がかかりました。 表紙制作に苦戦したため、入稿が3週間も後ろ倒しになってしまいました。 印刷所の方ごめんなさい。

表紙作成の苦労話

これまで表紙は知り合いのデザイナーさんにお願いしていたのですが、今回は諸事情あって頼めなかったので自力で作りました。

本編の執筆が押していた今回は、なるべく楽して描き上げたいということで、Stable Diffusion に手を出しました。 これが泥沼の始まりでした。

ローカルに環境を用意して、UIの使い方を覚えるだけでは終わらず、モデルやLoraの選定、プロンプトのチューニングなど、まあ言ってしまえば薄い本が1冊かけるくらいの勉強が必要でした。 思い通りのキャラクター、いわゆるうちの子が出せるようになるまで研究を重ねるのにも苦労しましたが、そこで終われなかったのが大変な所でした。

私はデザインのプロではないですが、各種本の表紙を見ると、タイトルやサークル名を入れるスペースが確保されています。 ところが、Stable Diffusion で何も考えなしに画像を出力すると、枠全体にキャラクターが描画されてしまいます。 プロンプトの工夫で対処もできるんでしょうが、そのための研究とガチャを引き続ける根気は残っていませんでした。

そこで結局 ControlNet を使って、アウトラインだけ書いてディテールはAIに任せようという方針にしました。

ControlNetは Style2Paints を作っていた研究チームが開発した、既存の画像を使って新たに生成する画像のポーズなどを指定できる技術です。

ControlNet を使えば、ダイレクトに所望の構図が生成できるので、闇雲にガチャを引き続けるストレスからは解放されました。

下書きから線画へ
線画から着色をし、img2imgで微調整

タイトルとサークル名、帯を入れて完成

でも草案とはいえ、これだけデザインとプロンプトの研究が必要なんであれば、正直最初から自分で描いた方が早かったかもですね。 まあ、私は塗りや細かい書き込みが苦手なので、これでも少しは楽できたのかもしれません。

文章面では、ChatGPTの力も借りようとしました。 上手いこと使いこなしているサークルさんもいらっしゃいましたが、私は結局自分の文体や強調したい部分のこだわりが強くて、上手く活用できませんでした。

とはいえ、タイトル決めの壁打ち役としては活躍してくれました。

オフライン当日の話

オフライン参加はこれで2度目になるのですが、mochikoAsTechさんやミライ・ハッキング・ラボさんの持ち物リストを参考に準備をしました。 note.com

akademeia.info

幟やデモ機などの備品はなく、こじんまりとしたものだけなのでサークル入場15分前に着けば余裕をもって設営できました。 それでも、本の種類も増えてだいぶ賑やかになってきました。

お恥ずかしながら、後々知ったのですが、まさかお隣がプロの作家さんだったとは!

note.com

開場してからは、整理券のおかげで定期的な波はあるものの、コミケに比べるとスムーズに移動できるのでよかったです。

事前のチェックイン数がわからないので、どのくらいの方がスペースに来てくれるかドキドキでしたが、 知り合いの方を始めとしていろいろな方が来てくれました。 印刷所さんや出版社さん、運営の方々などにもスペースに来ていただいたのですが、ことごとく私が不在のタイミングですみませんでした。

手に取っていただいた方とは、本の分厚さのことはもちろん、 「Solrの本、こと日本語だとぜんぜんないですよね」とか「うちでもちょうど導入を考えていて」といったお話ができて楽しかったです。 やっぱりSolrって知名度あるのに、名著を除くと書籍があまり出回っていないというのは共通認識なんですね。 そんな方々に届いて役立つ1冊になっているなら幸いです。

中には、「これから全文検索を始めたいと思っています」という方もいらっしゃいました。 本の主題は全文検索の次のステップとしてベクトル検索だったんですが、全文検索そのものに一定層の需要があるというのは意外でした。 いっそのことSolrの入門本の方が需要あったんだろうか?

ありがたいことに既刊の物理本は無事完売しました。 売り切れた後も物理本を求めてくださる方がいたのですが、泣く泣く電子版をご案内したのは申し訳なかったです。 でも、印刷費を考えるとまとまった量の需要がないと重版の予定はないんですよね... う~む、悩ましい。

お店番については、やっぱり2人態勢がおすすめです。 最初は1人で店番をやろうかと思っていたのですが、ダメもとで友人に頼んだら快諾してくれたので助かりました。 Tくんありがとう。

ワンフロアの端から端まで回ろうとすると、スペースを離れる時間が長くなってしまうのでワンオペにならなくてよかったです。 これでもコンパクトになった方で、コロナ前は2フロアだったことを思い返すと、当時全スペース回ろうと考えると恐ろしい限りですね。

あと、たま~に、現金払いを求められるのですが、 そろそろ現金対応を検討したほうがいいんですかね?

ほかの方のスペースにお邪魔したときに感じたのですが、名刺はあってもよかったかなと思います。

戦利品

事前にチェックしていたものに加えて、スペースでお話ししていたら、ついつい買ってしまいました。 みなさん、トークお上手ですね。

ちょっとずつ消化していかないと。

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

techbookfest.org

今後に向けて

お祭り自体は大盛況のなか閉幕しましたが、引き続きオンラインショップは随時稼働中です。 電子版でよければ頒布しているので、覗いていってください。 質問、感想お待ちしております。

また、メールあんま見ないなや日別の頒布数の分析したいなという思いから、 サークルの方に届く購入通知メールをSlackに転送+グラフ化するツールを作りました。

頒布通知

会期中に公開までは間に合いませんでしたが、次回までには公開したいと思います。

さて、次回の技術書典15は11/11~とのことですが、テーマどうしましょうか? ベクトル検索を深堀するか、意外と全文検索に興味があるという方もそこそこいらっしゃったので、Solrのチュートリアル本にするか、それともまた違ったネタにするか... う~ん、現状これといったテーマがないんですよね。 もし、この記事を読んでくださった奇特な方がいらっしゃれば、要望もらえると助かります。 書く保証はできませんが、候補には確実に入りますので。

本ブログの次の更新がいつになるかわかりませんが、また次の技術書典には出たいと思っているので、その時にでもお会いできれば!

それでもはてなブログを自分のホームページに埋め込みたい

2021/1/31に、CORS制限回避のために使っていた https://github.com/Rob--W/cors-anywhere が悪用が多すぎるために利用を制限したようです。 ※教えてくださった読者さんありがとうございます

github.com

この関係で、前回の方法ではRSSの取得ができなくなってしまいました。 t-n-clark.hatenadiary.jp

下記記事にも書かれている通り、本来、フロントエンドから直接外部サービスのAPIを呼ぶのは邪道であって、自前でAPIを立てるのが正攻法のようです。 medium.com career.levtech.jp

そこで、はてなブログAPIを叩くバックエンドのAPIを用意して、それをフロントエンドから呼びに行くようにすることにしました。

※ちなみにフロントエンド(React)から直接はてブAPIを呼び出そうともしてみましたが、同じくCORSエラーに遭遇しました。 そりゃそうですよね。

APIGCPAWS、Herokuなど好きな場所に立てればいいのですが、サーバの管理が不要でデプロイが簡単なGoogle Apps Script(以下、GAS)で作ることにしました。

※GASの使い方などについては、さまざまな記事で紹介されているので、ここでは割愛します。

まずは、はてブAPIを使うための準備をします。

はてブAPIは使用用途別にいろいろな種類が用意されているので、用途に合ったものを選択します。 developer.hatena.ne.jp 使用する際は、利用規約に気を付けましょう。 developer.hatena.ne.jp

今回はお試しなので、一番認証が簡単なBasic認証を選びました。 developer.hatena.ne.jp

Basic認証はDeveloper登録不要で使えます。

より強力なセキュリティ認証であるOAuth認証を使う場合はDeveloper登録が必要です。 登録方法は下記を参考にしてください。 developer.hatena.ne.jp

Basic認証APIキーはブログの管理画面から調べられます。 合わせてルートエンドポイントも確認しておきます。 http://blog.hatena.ne.jp/my/config/detail

はてブAPIの使い方がわかったので、続いて、GASではてブAPIを叩いて必要情報を返却する自前APIの実装をしていきます。

GASから取得するサンプルはこの記事が参考になります。 といってもただ単純にfetchしているだけです。 www.kotanin0.work

取得結果の細かい仕様は下記の記事が参考になります。 korosuke613.hatenablog.com

例えば、こんな感じで取ってこれます。 your_root_endpointyour_api_keyのところには先ほど管理画面で確認したルートエンドポイントとAPIキーが入ります。

function fetchHatenaFeeds() {
  const url = "your_root_endpoint/entry";
  const username = "your_username"
  const apiKey = "your_api_key"
  const options = {
    "method": "GET",
    'headers' : {'Authorization' : 'Basic ' + Utilities.base64Encode(username + ':' + apiKey)}
  }
  var xml = UrlFetchApp.fetch(url,options).getContentText();
  return xml;
}

変数名からわかるようにはてブAPIから取得できる情報はXML形式になっています。 このままだと扱いにくいので、必要な情報だけ抜き出しでJSON形式に変換してしまいましょう。

function parseXmlContent(xml){
  var contentList = [];
  var entries = xmlToJson(xml).feed.entry;
  entries.map((entry)=>{
    if(entry.control.draft.Text === "no"){
      contentList.push({
        "title": entry.title.Text,
        "pubDate": entry.published.Text,
        "link": entry.link[1].href,
      })
    }
  })
  return contentList
}

こんな感じでxmlToJson()でエントリーを変換して、所望のカラムを取り出して連想配列に格納します。 この例では、

  • title
  • pubDate
  • link

を最終的に返す仕様になっています。

xmlToJson()の中身は以下の通りです。 こちらのサンプルコードをそのまま使わせてもらいます。 gist.github.com

データ取得~返却用の連想配列に変換までの流れをまとめて1つの関数にしてしまいましょう。

function getHatenaFeeds(){
  var xml = fetchHatenaFeeds();
  var contentList = parseXmlContent(xml);
  return contentList;
}

最後にGETリクエストの受け付け関数を用意します。 GASの場合はdoGetという名前の関数がエンドポイントになります。

GASではあらかじめ登録された名前の関数を作ることで、 イベントをトリガーにスクリプトを実行できる シンプルトリガーという仕組みがあります。

今回の場合は、フロントエンドからのGETリクエストがイベントになります。

上記で作ったgetHatenaFeeds()で取得した情報をJSON形式に変換して返しています。

function doGet(e){
  var data = getHatenaFeeds();
  var payload = JSON.stringify(data);
  var output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.JSON);
  output.setContent(payload);

  return output;
}

これら一連のスクリプトをデプロイして公開すれば、自分のはてブRSS情報が取得できるAPIが完成です。

あとは、前回の実装でRSSParserを使っていたところをaxiosに取り替えてやれば、OKです。

www.npmjs.com

前回

urls.map((url) => {
            rssParser.parseURL(url)
                .then((feed) => {
                    // 新たに取得したfeedを取り出す

今回

urls.map((url) => {
            axios.get(url)
                .then((feed) => {
                    // 新たに取得したfeedを取り出す

これで同じ状態に復旧できました。やった~!

※ホームページお引越ししました。 sashimimochi.netlify.app

ポッドキャスト始めてみたくてReact触ってみた

経緯とか

最近、Tech系のポットキャストを趣味で聴き始めたのですが、そんな話を仲間内でしたら「自分たちでもやってみない?」ということになったのでReact使って公開用のページを作ってみました。とはいえ、React初心者が作ったものなので、クオリティはお察しです。 今回の目標は、 - Youtubeの動画を埋め込む - 毎回の放送をカードで表示する - カードの内容をGoogle Sprede Sheetから取ってくる - Twitterのタイムラインを埋め込む

ができれば最低限の体裁はできるかなと言うことで、取っ付きやすそなのから順番に試して意図通りに動いたら即採用。なので、もっといい方法はきっとあると思いますが、そこは今後追求します。

環境構築etc.

React初心者なので、ディレクトリ構成とかBabelとか言われてもちんぷんかんぷんなので、その辺りをよしなにしてくれるcreate-react-appを使うことにします。なんでもfacebookが提供しているお手軽プロジェクトビルダーだとか。インストールにはこちらを参考にしました。 Windows+VSCode上でReact開発環境を構築 Macの場合は私はこんな感じでインストールしました。 MacでReactの環境を作ろうと思ってコケた あとはこのあたりを参考にざっと斜め読みして、書き方のお作法を勉強しました。 jQueryを卒業したかった僕がReact StaticでReactをイチから学んでWebサイトを作った話

Youtubeの動画を埋め込む

React Player 日本語の参考ページがみたい方はこちら Reactアプリケーションで動画や音楽ファイルを扱うために「react-player」を使用する ページにしたがって、

$ npm install react-player --save

でインストールする。今回は音声ファイルはYoutubeにあげるつもりなので、Youtubeの動画を再生できればOKにします。React Playerはいろんな形式の動画を再生できそうなんだけど、Youtubeは専用のPlayerが用意されているようなので、そいつを使うことに。これも公式ページの通りに組んでみると、ちゃんと再生できたし、シークバーも表示されました。純粋なReact Playerだとシークバーは出ませんでした。

毎回の放送をカードで表示する

そろそろBootstrapを卒業しようと思うので、Reactと相性がいいらしい、Material UIを使ってみることにします。 【React】Material-UIでReactアプリケーションをマテリアルデザイン化する もっと凝ったUIを作りたい場合は公式ページにソース付きでデモがあるのでお試しあれ。 Material-UI Cards 他の候補としてはこの辺りがおすすめぽいです。 無料のマテリアルデザインフレームワーク10選

カードの内容をGoogle Sprede Sheetから取ってくる

これが正直一番難しかったです。最終的に取った手段が 1. Google Spread SheetをGoogle Apps Script(GAS)で取得&JSON形式で返す 1. ReactでJSONを読み込んで所望の形式に変換して表示

でした。 まず、こんな感じのSpread Sheet を用意します。

id title url description
No.1 Title1 https://www.youtube.com/watch?v=*** その回の説明を書く
No.2 Title2 https://www.youtube.com/watch?v=*** 説明2
No.3 Title3 https://www.youtube.com/watch?v=*** 説明3

これをGASでJSON形式でGETリクエストに対して返してあげるようにします。こちらを参考に実装しました。 Google App Scriptを用いてGoogleスプレッドシートからJSONを生成してみよう 実践編の通りにGASを書いて1行目をkeyにしてこんな感じのJSONを取得できるようにしました。

[{"id":"No.1","title":"Title1","url":"https://www.youtube.com/watch?v=***","description":"その回の説明を書く"},{"id":"No.2","title":"Title2","url":"https://www.youtube.com/watch?v=***","description":"説明2"},{"id":"No.3","title":"Title3","url":"https://www.youtube.com/watch?v=***","description":"説明3"}]

セキュリティ的にはガバガバですが、まあ、これで半分クリアです。残りは、React側での受け取り処理を作ります。今回はaxiosを使います。Reactから手軽にHTTPリクエストが送れるライブラリのようです。 例によって参考ページはこちらです。 React+axiosでLaravel APIからJSON取得 axiosで取得して、先ほどのCardの中に流し込みます。

{// 一部抜粋
}
import axios from 'axios';
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      contents: []
    };
    this.getData = this.getData.bind(this);
  }

  getData() {
    axios
    .get('GASの公開URL')
    .then(results => {
        const data = results.data;
        console.log(data);
        this.setState({
          contents: [...data]
        });
      });
  }

  render() {
    const classes = this.props.classes;
    console.log(classes);

    const contents = this.state.contents.map(content => {
      return <Card className={classes.card}>
          <CardContent>
            <h2 key={content.id}>{content.title}</h2>
            <div align="center" key={content.id}>
              <YoutubePlayer
                url={content.url}
                controls
              />
            </div>
            <p key={content.id}>{content.description}</p>
          </CardContent>
        </Card>
    });

    return (
      <Fragment>

      <div className="App">
          <button onClick={this.getData}>getData</button>
          {contents}
      </div>
      </Fragment>
    );
  }
}

export default withStyles(styles)(App);

基本的にはこれでできたのですが、このままだとユーザーが毎回ボタンを押さないとGASから読み込みが行われないので、ページロード時点で読み込めるようにします。やりたいこととしてはonLoadのようなことをしたいのですが、Reactの場合は、componentDidMountをよく初期化に使うと聞いたので、これを使います。本当は使い方気をつけないと無限ループとは発生して危ないらしいのですが、冒頭の通り動いているからまあいっかの精神で書きます。 Reactのライフサイクルメソッドとその使いドコロのまとめ - ajax callをするのに最も適した場所は? 先ほどのコードでgetDataの部分を全てcomponentDidMountに置換します。<button>の部分はいらないので削除します。これでリロードした度にGASから取得して表示してくれるようになりました。コンテンツ量が増えると崩壊しそうなので、そこはページを繰る度にロードするようにするなど要改善です。

その他参考になりそうな記事はこちら。

参考文献

Twitterのタイムラインを埋め込む

React Twitter Wigets ページのチュートリアルにしたがって、

$ npm i react-twitter-widgets

で落としてきて埋め込みます。なんとお手軽で、screenNameusernameに埋め込みたいアカウント名(@で始まるやつ)を書くだけ。たぶんsourceTypeを変えれば表示内容も変えられると思われる。今回は自分のアカウントのツイートを表示したいだけなので、デフォルトのprofileにしてます。

今回実装した全体のコード

以上、これで当初の目的は達成できました。 全体のコードを晒しておきます。コンポーネント化は度外視なので全部App.jsに書いてます。 肝心の放送内容はこれから収録します...近いうちに投稿したいなあ...もし、無事投稿できたらそちらもよろしくお願いします。

import React, { Component, Fragment } from 'react';
import axios from 'axios';
import ReactPlayer from 'react-player';
import YoutubePlayer from 'react-player/lib/players/YouTube';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardAction from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import { Timeline } from 'react-twitter-widgets';
import logo from './logo.svg';
import './App.css';
import { getConfig } from 'react-player/lib/utils';

const styles = {
  card: {
    margin: 20,
    height: 580
  },
};

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      contents: []
    };
    this.componentDidMount = this.componentDidMount.bind(this)
  }

  componentDidMount() {
    axios
      .get('GASの公開URL')
      .then(results => {
        const data = results.data;
        console.log(data);
        this.setState({
          contents: [...data]
        });
      });
  }

  render() {
    const classes = this.props.classes;
    console.log(classes);

    const contents = this.state.contents.map(content => {
      return <Card className={classes.card}>
          <CardContent>
            <h2 key={content.id}>{content.title}</h2>
            <div align="center" key={content.id}>
              <YoutubePlayer
                url={content.url}
                controls
              />
            </div>
            <p key={content.id}>{content.description}</p>
          </CardContent>
        </Card>
    });

    return (
      <Fragment>

      <div className="App">
        <AppBar position="static" color="default">
          <Toolbar>
            <Typography variant="title" color="inherit">
              Page Title
            </Typography>
          </Toolbar>
        </AppBar>
        <header className="App-header">
          <h1>
            Welcome to Our Site
          </h1>
          <p>
            Abstract about this page.
          </p>
        </header>

          {contents}

          <Timeline 
            dataSource={{
              sourceType: 'profile',
              screenName: 'Sashimimochi_343'
            }}
            options={{
              username: 'Sashimimochi_343',
              width: '50%',
              height: '400'
            }}
            onLoad={() => console.log('Timeline is loaded!')}
          />
      </div>
      </Fragment>
    );
  }
}

export default withStyles(styles)(App);

その他の参考文献

WindowsでもAlfredが使いたい

何がしたいのか?

ご時世も相まって、家で作業することが増えてきました。 普段仕事では、Macを使っているのですが、プライベートで使っているデスクトップはWindowsなので、Windowsで作業する機会も増えました。

Macでの作業に慣れてくると、その便利さゆえ、Windows環境でもSpotlightやAlfredのような、ランチャーがほしくなってきました。

果たしてそんな都合の良いアプリがあるのかと探したところ、ドンピシャなものがありました。

Keypirinha

f:id:T-N-Clark:20200920194410g:plain
keyprinha

さすがに完全互換とまでは行きませんが、Chromeのブックマーク検索など個人的に使いたい機能は概ね使えたので満足しています。

インストールVerとポータブルVerがありますが、公式はポータブルVerを推奨しています。 Python製らしく、外部パッケージもあり、自作もできるようです。


2022/2/9追記

お恥ずかしながら最近知ったのですが、PowerToysというツールがマイクロソフトからリリースされているようです。 github.com

PowerToysの中にSpotlight検索機能が内包されているみたいです。 使ったことがないので使い勝手の比較はできませんが、Spotlight以外にもたくさんの機能をもっているようなので、こちらを使ってもいいかもしれません。

各種機能についてはこちらの記事でわかりやすく日本語で解説されています。 qiita.com


セットアップ

インストールは不要で、OSのバージョンに合わせて適当なファイルをダウンロードしてくるだけで使えます。ダウンロードした圧縮ファイル内のkeyprinha.exeを実行するだけで起動します。 http://keypirinha.com/download.html ※圧縮形式が.7zなので、必要に応じて7-Zipなどの解凍ソフトを使って解凍してください。

PC再起動のたびに毎回Keyprinhaを起動するのも面倒なので、タスクマネージャーなどでスタートアップに追加しておくと便利です。

Keypirinhaの設定は、GUI設定画面もなければ、基本的な情報は英語のみなので、ちょっと敷居が高いようにも感じます。 とは言え、プログラマであればそこまで身構える必要はないと思います。

  1. まず、Keypirinhaのランチャーを起動して、Configureで設定ファイルを開きます。 デフォルトだとCtrl+Win+Kがランチャー起動のショートカットキーとして設定されています。
  2. すると、ウィンドウが2つ起動します。 左が読み取り専用のオリジナルで、右がユーザー設定用です。
  3. これを編集して、設定を変更します。
  4. 編集が終ったら、ウィンドウを閉じてKeypirinhaランチャーでReload Configurationと入力すれば変更が反映されます。

最低限、ショートカットキーの変更とGoogle検索あたりは設定しておくと便利です。 詳しい設定方法は以下が参考になります。

Microsoft Store でインストールしたアプリを起動する

デフォルトの検索範囲にはないので、Microsoft Store でインストールしたアプリはKeypirinhaのランチャーで検索してもヒットしません。

そこで、以下のプラグインを追加します。 https://github.com/ueffel/Keypirinha-WindowsApps

そのためには、プラグインの追加ができるようにPackageControlを使えるようにする必要があります。 https://github.com/ueffel/Keypirinha-PackageControl

手順

READMEの案内にしたがって

  1. Keypirinhaランチャーを起動してKeypirinha: Consoleまたは (Shortcut: F2)と入力する
  2. 起動したコンソール画面下部の入力欄に以下のコマンドを入力してEnterで決定する (SublimetextのPackageControlと同じような感じ)
import keypirinha as kp,keypirinha_net as kpn,os;p="PackageControl.keypirinha-package";d=kpn.build_urllib_opener().open("https://github.com/ueffel/Keypirinha-PackageControl/releases/download/1.0.2/"+p);pb=d.read();d.close();f=open(os.path.join(kp.installed_package_dir(),p),"wb");f.write(pb);f.close()
  1. 再度Keypirinhaランチャーを起動して、Install Packageと入力するとPackageControl: Install Packageが選択できるようになっているので選択する
  2. Keypirinha-WindowsAppsを選択してインストールする
  3. 再度Keypirinhaランチャーを起動して、所望のアプリを入力すると検索結果にアプリが表示されるようになる

その他参考文献

Macでもエアロスナップが使いたい

はじめに

エアロスナップとは、Windowsユーザーならご存知であろう、Windows7から搭載された機能です。 画面サイズに合わせてブラウザやアプリなどのウィンドウサイズを画面半分などに自動で調節してくれる機能です。

f:id:T-N-Clark:20200920193308g:plain
aerosnap

常にデュアルディスプレイにできるのであれば不要な機能かもしれませんが、画面の制約や真横に並べて見比べたいときなどは重宝します。 ブラウザで調べものしながら、エディターで編集するときなどはとても便利です。 個人的には便利だと思っているですが、好き嫌いは分かれるようで、あえて無効にしている人も少なくないようです。

プライベートはWindowsで、仕事ではMacを使っている私はMacでの作業中もこのエアロスナップが使いたくなる場面がしばしばありました。 一応、Macのデフォルト機能であるSplit Viewを使えば、それっぽいことはできるのですが、キーボード操作だけで調節できない、ウィンドウがフルスクリーンモードになってしまうなど、少々使い勝手に満足いかない部分がありました。 https://support.apple.com/ja-jp/HT204948

そこで、2つのアプリを試してみました。

Gridsutra Lite

  • Lite版は無料、フルサイズ版は有料($4.99)
  • Lite版はブラウザ、テキストエディタ、プレビューアプリのみ対応
  • ショートカットキー対応

Apple Storeからお手軽にインストールというところで試してみました。 決して悪くはなかったのですが、Lite版だと物足りなかったので、フルサイズ版にするか悩みましたが、次に紹介するアプリで事足りてしまったので、結局不採用。

Spectacle

f:id:T-N-Clark:20200920193316g:plain
spectacle

  • 基本無料
  • 任意のウィンドウに対してリサイズが可能
  • ショートカットキーに対応

機能はシンプルですが、ほぼエアロスナップと同等のことができます。

おわりに

Spectacleのおかげで、Windowsのときと同じ使用感でMacでも作業できるようになりました。 みなさんも、私と同じようなもやもやを抱えているようであれば、一度お試しあれ。

ちなみに、試してはいませんが、ほかにも以下のような類似アプリもあるようです。