Cond0r 发布的文章

鼠标用的是MX2,支持Logi Flow,但是键盘是HHKB自己该的YDKB的蓝牙主控,现在想在俩台电脑之间切换鼠标的时候也能自动切换键盘。

想到一个简单的解决方案,就是在MX2切换到另一个设备的时候,当前机器的蓝牙会断开链接,那么我只要发现一旦鼠标断开链接同时也就断开键盘的链接,然后再另一台电脑上判断一旦发现鼠标链接进来了,就自动链接蓝牙键盘

需要先安装blueutil和bluetoothconnector

brew install bluetoothconnector
brew install blueutil

1.0 版本

基于这个逻辑写了一个简单的bash脚本,但是发现挂后台不是很方便

#!/bin/bash

ConnectHHKB(){
    check=`blueutil --connected|grep HHKB|wc|awk '{print$1}'`
    if [ $check == "0" ]
    then
        bluetoothconnector --connect fb-fd-74-d3-60-61
    fi
}

DisConnectHHKB(){
    check=`blueutil --connected|grep HHKB|wc|awk '{print$1}'`
    if [ $check == "1" ]
    then
        bluetoothconnector --disconnect fb-fd-74-d3-60-61
    fi
}
check(){
    check=`blueutil --connected|grep MX|wc|awk '{print$1}'`
    if [ $check == "0" ]
    then
        DisConnectHHKB
    else
        ConnectHHKB
    fi
}
while True
do
    check
    sleep 0.1
done

hammerspoon版本

因为YDKB断开后会自动链接另一个设备,我只要去执行一个disconnect就可以了
然后写一个hamm的init.lua

local watcher = hs.timer.new(1, function ()
    local m = hs.mouse.count()
    if m==2 then
        hs.execute("bash ~/.hammerspoon/disconnect.sh",true)
        print("mouse disconnect")
    end
    print(m)
    -- hs.alert.show(m)
end)
watcher:start()

实际disconnect.sh

check=$(blueutil --connected | grep HHKB | wc | awk '{print$1}')
if [ $check == "1" ]; then
    bluetoothconnector --disconnect fb-fd-74-d3-60-61
fi

参考资料

https://www.hammerspoon.org/docs/hs.html
https://www.ahonn.me/blog/how-to-implement-clipboard-history-with-hammerspoon
https://www.hammerspoon.org/docs/hs.timer.html
https://www.hammerspoon.org/go/#helloworld

记一次go的http.client高并发踩坑记

最早在写求索的时候就遇到了同样的问题,但是因为求索并发并不是特别高,所以不是每次都能复显,我也不想一直挂着dlv去等。
最近在写一些扫描、爆破的小工具,遇到相同的问题,折腾了2天终于解决记录一下。
大概会出现这样的错误

net/http.(*persistconn).writeloop(0xc00b3498c0)

其实是3个问题

1. 创建大量client之后导致的协程溢出

这个问题有俩个原因

1.1 重复创建transport

默认使用http.Default.Get/http.NewRequest,都会创建一个新的transport,导致进行了大量的不必要的开销。
其实transport是可以复用的,本身http.client就是一个请求池

var transport http.Transport
func init() {
    transport = http.Transport{
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: true,
        },
    }
}
func (h *Http) Execute() *http.Response {
    defer func() {
        if err := recover(); err != nil {
            log.Println(err)
        }
    }()
    var err error
    //复用transport
    h.HttpClient.Transport = &transport
    h.HttpResponse, err = h.HttpClient.Do(h.HttpRequest)
    if err != nil {
        log.Println("[!] Http Execute Error : ", err)
        h.HttpResponse = nil
        return nil
    }
    return h.HttpResponse
}

1.2 HttpResponse.Body未完成完全读取

一般是开了大量的goroutine去执行http.client,但是最后导致client没有自动回收、关闭,导致goroutine的崩溃

goroutine 退出需要满足:

  • body 读取完毕
  • request 主动 cancel
  • request context Done 状态 true
  • 当前的 persistConn 关闭

比较常见的场景就是我们只去获取了header,并没有去读取body,然后就return了,据说不把body读完也会导致同样的问题
为了以防万一,我们把request也设置一个context进行手动Done

// 新建一个请求
func (h *Http) New(method, urls string) error {
    var err error
    //设置一个context
    h.Ctx, h.CtxCancel = context.WithCancel(context.Background())
    ....
    h.HttpRequest, err = http.NewRequest(h.HttpRequestType, h.HttpRequestUrl, h.HttpBody)
    h.HttpRequest.WithContext(h.Ctx)
    return err
}
// 关闭请求与body
func (h *Http) Close() {
    defer func() {
        if err := recover(); err != nil {
            log.Println(err)
        }
    }()
    
    if h.HttpResponse != nil {
       //读取内容并关闭body
        h.readAll()
    }
    if h.CtxCancel != nil {
       //主动停止request
        h.CtxCancel()
    }

}

2. 内存溢出

内存溢出的原因是因为一般我们读body都是这样的

resp:=make([]byte,512)
ioutil.ReadAll(HttpResponse.Body,resp)

这样每次读取一个新的都会进行一次makeslice操作,导致内存使用率增大,最后被系统强制killer

解决方案就是使用sync.Pool,创建一个byte的pool,每次都从pool里面去取空闲的出来使用

var pool sync.Pool
func init(){
//初始化一个pool
    pool = sync.Pool{
        New: func() interface{} {
            return bytes.NewBuffer(make([]byte, 4096))
        },
    }
}
.....
func (h *Http) readAll() ([]byte, error) {
    //获取一个新的,如果不存在则会调用new创建
    buffer := pool.Get().(*bytes.Buffer)
    buffer.Reset()
    defer func() {
        if buffer != nil {
            //重新放回去
            pool.Put(buffer)
            buffer = nil
        }
    }()
    if h.HttpResponse == nil {
        return nil, fmt.Errorf("HttpResponse is nil")
    }
    if h.HttpResponse.Body == nil {
        return nil, fmt.Errorf("HttpResponse.Body is nil")
    }
    _, err := io.Copy(buffer, h.HttpResponse.Body)
    if err != nil && err != io.EOF {
        //log.Printf("readAll io.copy failure error:%v \n", err)
        return nil, fmt.Errorf("readAll io.copy failure error:%v", err)
    }
    defer h.HttpResponse.Body.Close()
    return buffer.Bytes(), nil
}

参考资料

https://barbery.me/post/2019-08-02-fix-goroutine-memory-leak/
https://sanyuesha.com/2019/09/10/go-http-request-goroutine-leak/
http://xiaorui.cc/archives/7172
https://riptutorial.com/go/example/16314/sync-pool
https://studygolang.com/articles/16282

GORM不使用数据绑定获取任意SQL获取结果

在gorm中,想获取sql执行的结果,必须将返回结果绑定到slice或者struct中,但是如果我们想通用执行任意语句,在事先不知道表结构的情况下是没办法使用这个方式的,最后通过查阅资料发现一个曲线救国的方式

rows, err := db.Raw(sql).Rows()
    if err != nil {
        result = append(result, err.Error())
        return result
    }
    var colums []string
    for rows.Next() {
        //先获取所有的column
        if colums == nil {
            colums, _ = rows.Columns()
        }
        result = append(result, colums)
        //建立俩个interface数组,columnPointers中存在columns的地址
        columns := make([]interface{}, len(colums))
        columnPointers := make([]interface{}, len(colums))
        for i, _ := range columns {
            //赋值地址
            columnPointers[i] = &columns[i]
        }
        //扫描结果
        rows.Scan(columnPointers...)
        m := make(map[string]interface{})
        for i, colName := range colums {
            val := columnPointers[i].(*interface{})
            m[colName] = *val
        }

        //log.Println(m)
        result = append(result, m)
        //result = append(result, row)
    }
    return result
    //log.Println(result)

}

一开始不太好理解,然后去看了gorm Scan部分的代码
sql/sql.go

func (rs *Rows) Scan(dest ...interface{}) error {
    rs.closemu.RLock()
    ...一些不重要的代码...
    for i, sv := range rs.lastcols {
        //重点看convertAssignRows,是如何赋值的
        err := convertAssignRows(dest[i], sv, rs)
        if err != nil {
            return fmt.Errorf(`sql: Scan error on column index %d, name %q: %v`, i, rs.rowsi.Columns()[i], err)
        }
    }
    return nil
}

func convertAssignRows(dest, src interface{}, rows *Rows) error {
    // 通过反射找到具体类型
    var sv reflect.Value

    switch d := dest.(type) {
    //字符串指针
    case *string:
        .....
    case *[]byte:
        .....
    case *RawBytes:
        ......
    case *bool:
        .....
    //就是这里了,我们传进来的是指向columns的interface的指针,所以直接给columns的值修改了
    case *interface{}:
        *d = src
        return nil
    }

    ...一些不相关的代码...
}

参考资料

https://kylewbanks.com/blog/query-result-to-map-in-golang

https://gocn.vip/topics/2121

GO SMTP SSL链接出错问题

Golang自带了一个smtp模块,正常登录流程如下

hostname := fmt.Sprintf("%s:%s", host, port)  
domain := strings.Split(email, "@")[1]  
auth := smtp.PlainAuth("", email, password, domain)
smtpClient, err = smtp.Dial(hostname)  
if err != nil {  
   log.Println("Dial: ", err)  
   return false  
}
err = smtpClient.Auth(auth)  
if err != nil {  
   log.Println("Auth: ", err)  
   return false  
}

但是我发现在链接465即配置了SSL的smtp服务器会导致30秒之后直接返回一个EOF错误

最后通过翻阅smtp/smtp.go代码发现如下:

//默认使用该方法
func Dial(addr string) (*Client, error) {
    //先创建tcp链接
    conn, err := net.Dial("tcp", addr)
    if err != nil {
        return nil, err
    }
    host, _, _ := net.SplitHostPort(addr)
    //在进入NewClient
    return NewClient(conn, host)
}

// NewClient returns a new Client using an existing connection and host as a
// server name to be used when authenticating.
func NewClient(conn net.Conn, host string) (*Client, error) {
    text := textproto.NewConn(conn)
    _, _, err := text.ReadResponse(220)
    //其实这里已经失败了,所以返回一个EOF
    if err != nil {
        text.Close()
        return nil, err
    }
    c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
    _, c.tls = conn.(*tls.Conn)
    return c, nil
}

那么我们只需要创建一个tls.Dial,然后在通过tls的Conn去创建一个NewClient即可。

最后修复代码如下

func SmtpLogin(host, port, email, password string, ssl bool) bool {
    hostname := fmt.Sprintf("%s:%s", host, port)
    domain := strings.Split(email, "@")[1]
    auth := smtp.PlainAuth("", email, password, domain)
    var smtpClient *smtp.Client
    var err error

    if ssl {
        tlsconfig := &tls.Config{
            InsecureSkipVerify: true,
            ServerName:         host,
        }
        //创建一个tls链接
        if conn, err := tls.Dial("tcp", hostname, tlsconfig); err != nil {
            log.Println("tls.Dial: ", err)
            return false
        } else {
            smtpClient, err = smtp.NewClient(conn, domain)
        }

    } else {

        if tcpConn, err := net.Dial("tcp", hostname); err != nil {
            log.Println("net.Dial: ", err)
            return false
        } else {
            smtpClient, err = smtp.NewClient(tcpConn, domain)
        }
    }

    if err != nil {
        log.Println("smtp.NewClient: ", err)
        return false
    }

    err = smtpClient.Auth(auth)
    if err != nil {
        log.Println("Auth: ", err)
        return false
    }
    return true
}

参考资料

https://gist.github.com/jim3ma/b5c9edeac77ac92157f8f8affa290f45
https://bastengao.com/blog/2019/11/go-smtp-ssl.html
https://gist.github.com/chrisgillis/10888032
https://stackoverflow.com/questions/57063411/go-smtp-unable-to-send-email-through-gmail-getting-eof

yabai是一个macOS的上的平铺桌面程序,可以达到类似linux上i3wm之类的效果。

-w1920

需要开启显示器具有单独的空间

安装

# yabai
brew install koekeishiya/formulae/yabai

# spacebar 用于代替原生status bar,需在设置中设置为自动隐藏
brew install cmacrae/formulae/spacebar

# skhd 快捷键管理工具,可以用其他的程序代理
brew install koekeishiya/formulae/skhd

sudo yabai --install-sa

# 如果是Big Sur则还需要执行该行

sudo yabai --load-sa

启动

brew services start yabai
brew services start skhd
brew services start spacebar

配置文件

yabai配置

touch ~/.yabairc
chmod +x ~/.yabairc

配置文件内容

# bsp or float (default: bsp)
yabai -m config layout bsp

# Override default layout for space 2 only
# yabai -m config --space 2 layout float

# New window spawns to the left if vertical split, or top if horizontal split
yabai -m config window_placement first_child

# New window spawns to the right if vertical split, or bottom if horizontal split
yabai -m config window_placement second_child


# Set all padding and gaps to 20pt (default: 0)
yabai -m config top_padding    20
yabai -m config bottom_padding 20
yabai -m config left_padding   20
yabai -m config right_padding  20
yabai -m config window_gap     20

# Override gaps for space 2 only
# yabai -m config --space 2 window_gap 0

# on or off (default: off)
yabai -m config auto_balance on

# set mouse interaction modifier key (default: fn)
yabai -m config mouse_modifier fn

# set modifier + left-click drag to resize window (default: move)
yabai -m config mouse_action1 move

# set modifier + right-click drag to resize window (default: resize)
yabai -m config mouse_action2 resize

# set focus follows mouse mode (default: off, options: off, autoraise, autofocus)
yabai -m config focus_follows_mouse autoraise

# set mouse follows focus mode (default: off)
# yabai -m config mouse_follows_focus on

# floating windows are always on top (default: off)
yabai -m config window_topmost on

# modify window shadows (default: on, options: on, off, float)
# example: show shadows only for floating windows
yabai -m config window_shadow float

# window opacity (default: off)
# example: render all unfocused windows with 90% opacity
# 窗口透明度设置
yabai -m config window_opacity off
yabai -m config active_window_opacity 1.0
yabai -m config normal_window_opacity 0.95

# add 20 padding to the top and 0 padding to the bottom of every space located on the main display
yabai -m config external_bar main:20:0
# add 20 padding to the top and bottom of all spaces regardless of the display it belongs to
yabai -m config external_bar all:20:20


# 临时性的App 也就是manage=off不受到yabai平铺式的限制,之前怎么弹出来就怎么弹出来
otherApp='^(IINA|Calendar|日历|System Preferences|系统偏好设置|Xnip|GoLand|微信)$'
yabai -m rule --add app="${otherApp}" manage=off


space_1='(iTerm2)'
yabai -m rule --add label=space_1 app=$(space_1) space=2

# space_1='(iTerm2)'
# yabai -m rule --add label=space_1 app=$(space_1) space=1

yabai -m rule --add label=safari app="^Safari$" space=2

# ====== Border settings =======================
normal_border_color="010101"
focused_border_color="FFD700"
preselect_border_color="2d74da"


yabai -m config window_border               on
yabai -m config window_border_width         2
yabai -m config active_window_border_color  "0xE0${focused_border_color}"
yabai -m config insert_window_border_color  "0xE0${preselect_border_color}"
yabai -m config normal_window_border_color  "0x00${normal_border_color}"


skhd 配置

touch ~/.skhdrc
chmod +x ~/.skhdrc

配置内容

# opens iTerm2(暂时隐藏了,因为权限太高了,导致AS快捷键都捕获不到,热键冲突)
#alt - return : "${HOME}"/.config/yabai/scripts/open_iterm2.sh


# Show system statistics


# Navigation
alt - h : yabai -m window --focus west
alt - j : yabai -m window --focus south
alt - k : yabai -m window --focus north
alt - l : yabai -m window --focus east

# Moving windows
shift + alt - h : yabai -m window --warp west
shift + alt - j : yabai -m window --warp south
shift + alt - k : yabai -m window --warp north
shift + alt - l : yabai -m window --warp east


# Resize windows
shift+ cmd - h : \
    yabai -m window --resize left:-20:0 ; \
    yabai -m window --resize right:-20:0

shift + cmd - j : \
    yabai -m window --resize bottom:0:20 ; \
    yabai -m window --resize top:0:20

shift + cmd - k : \
    yabai -m window --resize top:0:-20 ; \
    yabai -m window --resize bottom:0:-20

shift + cmd - l : \
    yabai -m window --resize right:20:0 ; \
    yabai -m window --resize left:20:0

# Float and center window
shift + alt - c : yabai -m window --toggle float;\
                  yabai -m window --grid 4:4:1:1:2:2

# Equalize size of windows
lctrl + alt - 0 : yabai -m space --balance

# Enable / Disable gaps in current workspace
lctrl + alt - g : yabai -m space --toggle padding; yabai -m space --toggle gap

# Rotate windows clockwise and anticlockwise
alt - r         : yabai -m space --rotate 90
shift + alt - r : yabai -m space --rotate 270

# Rotate on X and Y Axis
shift + alt - x : yabai -m space --mirror x-axis
shift + alt - y : yabai -m space --mirror y-axis

# Set insertion point for focused container
shift + lctrl + alt - h : yabai -m window --insert west
shift + lctrl + alt - j : yabai -m window --insert south
shift + lctrl + alt - k : yabai -m window --insert north
shift + lctrl + alt - l : yabai -m window --insert east

# Float / Unfloat window (更换 layout 方式) w
shift + alt - space : yabai -m window --toggle float

# Restart Yabai
shift + cmd + alt - r :
    /usr/bin/env osascript <<< \
        "display notification \"Restarting Yabai\" with title \"Yabai\""; \
    launchctl kickstart -k "gui/${UID}/homebrew.mxcl.yabai"

# Make window native fullscreen
alt - f         : yabai -m window --toggle zoom-fullscreen
# lctrl + alt - s       : yabai -m window --toggle split
shift + alt - f : yabai -m window --toggle native-fullscreen

# toggle window split type
alt - e : yabai -m window --toggle split

# increase window size 竖直方向才能用 top  水平方向才能用 left
shift + alt - d : yabai -m window --resize left:-20:0
shift + alt - w : yabai -m window --resize top:0:-20


# decrease window size
shift + alt - a : yabai -m window --resize left:20:0
shift + alt - s : yabai -m window --resize top:0:20


# fast focus desktop (切换空间焦点)
 cmd + alt - x : yabai -m space --focus recent
 cmd + alt - z : yabai -m space --focus prev
 cmd + alt - c : yabai -m space --focus next
# alt - 1 : yabai -m space --focus 1
# alt - 2 : yabai -m space --focus 2
# alt - 3 : yabai -m space --focus 3
# alt - 4 : yabai -m space --focus 4
# alt - 5 : yabai -m space --focus 5
# alt - 6 : yabai -m space --focus 6
# alt - 7 : yabai -m space --focus 7
# alt - 8 : yabai -m space --focus 8
# alt - 9 : yabai -m space --focus 9
# alt - 0 : yabai -m space --focus 10

# create new space
ctrl - n : yabai -m space --create
ctrl - m : yabai -m space --destroy

# destroy empty spaces
shift + cmd + alt - w : echo "destroy empty spaces";\
                        yabai -m query --spaces \
                        | jq 'reverse | .[] | select((.windows | length) == 0) | .index' \
                        | xargs -I{} yabai -m space {} --destroy \;

# send window to space
ctrl + shift - 1 : yabai -m window --space 1
ctrl + shift - 2 : yabai -m window --space 2
ctrl + shift - 3 : yabai -m window --space 3
ctrl + shift - 4 : yabai -m window --space 4
ctrl + shift - 5 : yabai -m window --space 5
ctrl + shift - 6 : yabai -m window --space 6
ctrl + shift - 7 : yabai -m window --space 7
ctrl + shift - 8 : yabai -m window --space 8
ctrl + shift - 9 : yabai -m window --space 9
ctrl + shift - 0 : yabai -m window --space 10

# Move focus container to workspace
shift + cmd - m : yabai -m window --space last && yabai -m space --focus last
shift + cmd - b : yabai -m window --space prev && yabai -m space --focus prev
shift + cmd - n : yabai -m window --space next && yabai -m space --focus next
shift + cmd - 1 : yabai -m window --space 1 && yabai -m space --focus 1
shift + cmd - 2 : yabai -m window --space 2 && yabai -m space --focus 2
shift + cmd - 3 : yabai -m window --space 3 && yabai -m space --focus 3
shift + cmd - 4 : yabai -m window --space 4 && yabai -m space --focus 4
shift + cmd - 5 : yabai -m window --space 5 && yabai -m space --focus 5
shift + cmd - 6 : yabai -m window --space 6 && yabai -m space --focus 6


alt + ctrl - f : yabai -m space --layout float && \
                 yabai -m config window_border off
alt + ctrl - b : yabai -m space --layout bsp && \
                 yabai -m config window_border on



spacebar配置

需要先安装Font-Awesome字体

 mkdir -p ~/.config/spacebar
 touch ~/.config/spacebar/spacebarrc
 chmod +x ~/.config/spacebar/spacebarrc
 vscode ~/.config/spacebar/spacebarrc

配置文件内容

spacebar -m config position           top
spacebar -m config height             26
spacebar -m config spacing_left       25
spacebar -m config spacing_right      15
spacebar -m config text_font          "Helvetica Neue:Bold:13.0"
spacebar -m config icon_font          "Hack Nerd Font Mono:Regular:13.0"
spacebar -m config background_color   0xff202020
spacebar -m config foreground_color   0xffa8a8a8
spacebar -m config space_icon_color   0xff458588
spacebar -m config power_icon_color   0xffcd950c
spacebar -m config battery_icon_color 0xffd75f5f
spacebar -m config dnd_icon_color     0xffa8a8a8
spacebar -m config clock_icon_color   0xffa8a8a8
spacebar -m config space_icon_strip   1 2 3 4 5 6 7 8 9 10
spacebar -m config power_icon_strip    
spacebar -m config space_icon         
spacebar -m config clock_icon         
spacebar -m config dnd_icon           
spacebar -m config clock_format       "%Y-%m-%d %R:%S"

重启

brew services restart yabai
brew services restart skhd
brew services restart spacebar