20200808のGoに関する記事は4件です。

Goで画像をクリップボードに保存、読み取るライブラリを作った

こんにちわ
ゴリラです

画像をクリップボードにコピー、コピーした画像をクリップボードから読み取るGoのライブラリ clipboard-image をつくったので紹介します。
このライブラリはMac、Linux、Windowsで動作します。

先日 Goでソースコードを画像化するCLIを作った で紹介したCLIではこのライブラリを使っています。

使い方

関数は2つのみ

  • CopyToClipboard
  • ReadFromClipboard

画像ファイルをクリップボードにコピーするときは CopyToClipboard

image.png

クリップボードから画像ファイルを読み取るときは ReadFromClipboard

image.png

仕組み

クリップボードへのアクセスは外部コマンドを用いています。
それらをGoでラップしてライブラリとして提供しています。それぞれ次になっています。

OS コマンド
Mac OS osascript
Windows PowerShell
Linux xclip

Mac OSではApple Scriptを使えばクリップボードにアクセスできます。
Apple Scriptは標準でMac OSに入っているのでライブラリ意外は特に不要です。

# copy to clipboard
osascript -e 'set the clipboard to (read "/path/to/file.png" as TIFF picture)'

# write file from clipboard
osascript -e 'write (the clipboard as «class PNGf») to (open for access "/tmp/file.png" with write permission)'

WindowsではPowershellでクリップボードにアクセスできます。

# copy to clipboard
PowerShell -Command Add-Type -AssemblyName System.Windows.Forms;[Windows.Forms.Clipboard]::SetImage([System.Drawing.Image]::FromFile('/path/to/file.png'));

# write file from clipboard
PowerShell -Command Add-Type -AssemblyName System.Windows.Forms;$clip=[Windows.Forms.Clipboard]::GetImage();if ($clip -ne $null) { $clip.Save('/path/to/file.png') };

Linuxではxclipというコマンドをインストールことでクリップボードにアクセスできます。

# copy to clipboard
xclip -selection clipboard -t image/png < /path/to/file.png

# write file from clipboard
xclip -selection clipboard -o > /path/to/file.png

これらのコマンドをGoのexec.Commandで実行することで、Goでクリップボードにアクセスできます。

詰まったポイント

xclipを使う時、標準入力でファイルの中身を渡すのですが、次のように書いても標準入力に渡っていかないです。

image.png

標準入力に渡すにはStdinPipeでpipeを取得してファイルの中身を書き込んだ後に stdin を閉じる必要あります。
xclipの実装はatotto/clipboardを参考にしました。

image.png

さいごに

クリップボード周りは結構苦戦して、いろいろな方に質問してなんとか作れました。
ライブラリとしてはまだ完成形とは考えていなくて、今後も拡張していきたいと考えています。

ちなみに、この記事の画像はVimで書いたコードをcode2img.vimでクリップボードにコピーしてサクッとQiitaにアップロードしたものです。
当初手軽にVimでコードを画像化したいという目的を達成できて、実際使って便利ので満足しています。

コードブロックが使えないチャットやTwitterといったSNSでコード貼りたいときにぜひCLIを使ってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GoネットワークプログラムでIPv6対応させる】自分のグローバルアドレスを知る方法

理由

ネットワークプログラムで、自身のグローバルIPアドレスを知る必要がある場合がある。その場合のコード例をメモしておく

参考コード

main.go
package main

import (
    "fmt"
    "github.com/skubota/extip"
)

func main() {
    addr,err := extip.GetIP()
    if err != nil {
        fmt.Errorf("Address cannot get : %s\n", err)
    }else{
        fmt.Printf("Adress : %s\n",addr) 
    }
}
  • 結果(IPv6/IPv4対応クライアント)
Adress : 2xxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
  • 結果(IPv4のみ対応クライアント)
Adress : 2xx.xxx.xxx.xxx

解説

標準パッケージではないが、「skubota/extip」というIPv6対応のSTUNで通信のあったソースIPアドレスを知ることができます。

アンチパターン

あまり、これがアンチパターンだというものはないかと思いますが、IPv4のみの返答しかできないHTTPサーバやDNSサーバを使ったライブラリやコードがあるので注意が必要です。

まとめ

IPv6対応のパッケージ・コードとなっているか確認しよう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GoでIPv6】GoネットワークプログラムでIPv6対応させるためのアドレス+ポート組み合わせの作り方

理由

「IPアドレス:ポート番号」の文字列はいろいろな箇所で意味を持った使われ方をするので、よく利用する記法ではあるが、
Goで書かれたネットワークプログラムをIPv6に対応させるためには少し考慮する必要がある。その場合のコード例をメモしておく

参考コード

main.go
package main

import (
    "fmt"
    "net"
)

func main() {
    // 対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // 例として:80を付加していく
        ip := net.ParseIP(v)
        fmt.Printf("%d: %s = %s\n", i, v, net.JoinHostPort(ip.String(), "80"))
    }
}
  • 結果
0: 2001:db8::1 = [2001:db8::1]:80
1: 192.168.0.1 = 192.168.0.1:80
2: 2001:db8:0:0:1:0:0:1 = [2001:db8::1:0:0:1]:80
3: ::1 = [::1]:80
4: 2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa = [2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa]:80
5: 2001:db8::aaaa:0:0:1 = [2001:db8::aaaa:0:0:1]:80
6: ::192.0.2.1 = [::c000:201]:80

解説

net.JoinHostPortで結合をするだけ
なぜこのような処理にしなければいけないかは、アンチパターンを見てみましょう

アンチパターン

やってしまいがちなアンチパターンはそのまま文字列処理してしまうこと。

main.go
package main

import (
    "fmt"
)

func main() {
    // 対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // 例として:80を付加していく
        fmt.Printf("%d: %s = %s:%d\n", i, v, v,80)
    }
}

結果は以下の通りとなります
- https://play.golang.org/p/oHWvhw303lF

0: 2001:db8::1 = 2001:db8::1:80
1: 192.168.0.1 = 192.168.0.1:80
2: 2001:db8:0:0:1:0:0:1 = 2001:db8:0:0:1:0:0:1:80
3: ::1 = ::1:80
4: 2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa = 2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa:80
5: 2001:db8::aaaa:0:0:1 = 2001:db8::aaaa:0:0:1:80
6: ::192.0.2.1 = ::192.0.2.1:80

IPv6アドレスの場合のアドレスリテラル表記で処理しないことが問題となります

まとめ

IPv6アドレスをそのまま文字列結合するのはNG。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GoネットワークプログラムでIPv6対応させる】アドレス+ポート組み合わせの作り方

理由

「IPアドレス:ポート番号」の文字列はいろいろな箇所で意味を持った使われ方をするので、よく利用する記法ではあるが、
Goで書かれたネットワークプログラムをIPv6に対応させるためには少し考慮する必要がある。その場合のコード例をメモしておく

参考コード

main.go
package main

import (
    "fmt"
    "net"
)

func main() {
    // 対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // 例として:80を付加していく
        ip := net.ParseIP(v)
        fmt.Printf("%d: %s = %s\n", i, v, net.JoinHostPort(ip.String(), "80"))
    }
}
  • 結果
0: 2001:db8::1 = [2001:db8::1]:80
1: 192.168.0.1 = 192.168.0.1:80
2: 2001:db8:0:0:1:0:0:1 = [2001:db8::1:0:0:1]:80
3: ::1 = [::1]:80
4: 2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa = [2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa]:80
5: 2001:db8::aaaa:0:0:1 = [2001:db8::aaaa:0:0:1]:80
6: ::192.0.2.1 = [::c000:201]:80

解説

net.JoinHostPortで結合をするだけ
なぜこのような処理にしなければいけないかは、アンチパターンを見てみましょう

アンチパターン

やってしまいがちなアンチパターンはそのまま文字列処理してしまうこと。

main.go
package main

import (
    "fmt"
)

func main() {
    // 対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // 例として:80を付加していく
        fmt.Printf("%d: %s = %s:%d\n", i, v, v,80)
    }
}

結果は以下の通りとなります
- https://play.golang.org/p/oHWvhw303lF

0: 2001:db8::1 = 2001:db8::1:80
1: 192.168.0.1 = 192.168.0.1:80
2: 2001:db8:0:0:1:0:0:1 = 2001:db8:0:0:1:0:0:1:80
3: ::1 = ::1:80
4: 2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa = 2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa:80
5: 2001:db8::aaaa:0:0:1 = 2001:db8::aaaa:0:0:1:80
6: ::192.0.2.1 = ::192.0.2.1:80

IPv6アドレスの場合のアドレスリテラル表記で処理しないことが問題となります

まとめ

IPv6アドレスをそのまま文字列結合するのはNG。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む