diff --git a/client/client.go b/client/client.go index 904e21d..0fa1d29 100644 --- a/client/client.go +++ b/client/client.go @@ -116,6 +116,27 @@ func (c *client) Copy(text string) error { }) } +func (c *client) ImeOn() error { + c.logger.Debug("Request: ime On") + return c.withRPCClient(func(rc *rpc.Client) error { + return rc.Call("Ime.On", "dummy", dummy) + }) +} + +func (c *client) ImeOff() error { + c.logger.Debug("Request: ime Off") + return c.withRPCClient(func(rc *rpc.Client) error { + return rc.Call("Ime.Off", "dummy", dummy) + }) +} + +func (c *client) ImeToggle() error { + c.logger.Debug("Request: ime Toggle") + return c.withRPCClient(func(rc *rpc.Client) error { + return rc.Call("Ime.Toggle", "dummy", dummy) + }) +} + func (c *client) withRPCClient(f func(*rpc.Client) error) error { conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", c.host, c.port), c.timeout) if err != nil { diff --git a/go.mod b/go.mod index 2cceb09..1d3b644 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,19 @@ module github.com/lemonade-command/lemonade -go 1.16 +go 1.23.4 require ( - github.com/BurntSushi/toml v1.0.0 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect github.com/atotto/clipboard v0.1.4 github.com/go-stack/stack v1.8.1 // indirect - github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac - github.com/mattn/go-colorable v0.1.12 // indirect + github.com/inconshreveable/log15 v2.16.0+incompatible + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/monochromegane/conflag v0.0.0-20151130130520-6d68c9aa4183 github.com/pocke/go-iprange v0.0.0-20150823054938-08fbe355c365 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 2781a4c..dd835cd 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,24 @@ github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI= github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/inconshreveable/log15 v2.16.0+incompatible h1:6nvMKxtGcpgm7q0KiGs+Vc+xDvUXaBqsPKHWKsinccw= +github.com/inconshreveable/log15 v2.16.0+incompatible/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/monochromegane/conflag v0.0.0-20151130130520-6d68c9aa4183 h1:qiYr4zwNwHMPoaW4TF+h0ceJz5yKh4M4TSn9oXp8wpk= @@ -21,6 +30,12 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:s golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/lemon/cli.go b/lemon/cli.go index 82e8c9e..b13eedf 100644 --- a/lemon/cli.go +++ b/lemon/cli.go @@ -13,6 +13,9 @@ const ( COPY PASTE SERVER + IME_ON + IME_OFF + IME_TOGGLE ) const ( diff --git a/lemon/flag.go b/lemon/flag.go index 8b05207..5c110bd 100644 --- a/lemon/flag.go +++ b/lemon/flag.go @@ -61,6 +61,18 @@ func (c *CLI) getCommandType(args []string) (s CommandStyle, err error) { c.Type = SERVER del(i) return + case "ime_on": + c.Type = IME_ON + del(i) + return + case "ime_off": + c.Type = IME_OFF + del(i) + return + case "ime_toggle": + c.Type = IME_TOGGLE + del(i) + return } } @@ -97,7 +109,11 @@ func (c *CLI) parse(args []string, skip bool) error { if err != nil { return err } - if c.Type == PASTE || c.Type == SERVER { + if c.Type == PASTE || + c.Type == SERVER || + c.Type == IME_ON || + c.Type == IME_OFF || + c.Type == IME_TOGGLE { return nil } diff --git a/lemon/main.go b/lemon/main.go index 62e871c..6dd9fac 100644 --- a/lemon/main.go +++ b/lemon/main.go @@ -13,6 +13,9 @@ Sub Commands: copy [text] Copy text. paste Paste text. server Start lemonade server. + ime_on Enable IME. + ime_off Disable IME. + ime_toggle Toggle IME. Options: --port=2489 TCP port number diff --git a/main.go b/main.go index 018d73f..5de84d4 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,15 @@ func Do(c *lemon.CLI, args []string) int { case lemon.SERVER: logger.Debug("Starting Server") err = server.Serve(c, logger) + case lemon.IME_ON: + logger.Debug("IME On") + err = lc.ImeOn() + case lemon.IME_OFF: + logger.Debug("IME Off") + err = lc.ImeOff() + case lemon.IME_TOGGLE: + logger.Debug("IME Toggle") + err = lc.ImeToggle() default: panic("Unreachable code") } diff --git a/server/ime_lin.go b/server/ime_lin.go new file mode 100644 index 0000000..2a5c851 --- /dev/null +++ b/server/ime_lin.go @@ -0,0 +1,23 @@ +//go:build linux + +package server + +import ( + //"fmt" + //log "github.com/inconshreveable/log15" +) + +type Ime struct{ +} + +func (_ *Ime) On(_ string, _ *struct{}) error { + return nil +} + +func (_ *Ime) Off(_ string, _ *struct{}) error { + return nil +} + +func (_ *Ime) Toggle(_ string, _ *struct{}) error { + return nil +} diff --git a/server/ime_win.go b/server/ime_win.go new file mode 100644 index 0000000..e774cca --- /dev/null +++ b/server/ime_win.go @@ -0,0 +1,113 @@ +//go:build windows + +package server + +import ( + "fmt" + "syscall" + log "github.com/inconshreveable/log15" +) + +type Ime struct{ +} + +var logger log.Logger + +func init() { + logger = log.New() + logger.SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StdoutHandler)) +} + +var ( + user32 = syscall.NewLazyDLL("user32.dll") + imm32 = syscall.NewLazyDLL("imm32.dll") + getForegroundWindow = user32.NewProc("GetForegroundWindow") + sendMessage = user32.NewProc("SendMessageW") + immGetDefaultIMEWnd = imm32.NewProc("ImmGetDefaultIMEWnd") + immGetContext = imm32.NewProc("ImmGetContext") + immReleaseContext = imm32.NewProc("ImmReleaseContext") + immSetOpenStatus = imm32.NewProc("ImmSetOpenStatus") + immGetOpenStatus = imm32.NewProc("ImmGetOpenStatus") +) +const ( + WM_IME_CONTROL = 0x283 // IME control message + IMC_GETOPENSTATUS = 0x5 // Retrieve IME open/close status + IMC_SETOPENSTATUS = 0x6 // Set IME open/close status +) + +func getForegroundWindowHandle() syscall.Handle { + hWnd, _, _ := getForegroundWindow.Call() + return syscall.Handle(hWnd) +} + +func isIMEEnabled() (bool, error) { + hWnd, _, _ := getForegroundWindow.Call() + hIME, _, _ := immGetDefaultIMEWnd.Call(hWnd) + if hIME == 0 { + return false, fmt.Errorf("failed to get ime handle") + } + + ret, _, _ := sendMessage.Call(uintptr(hIME), uintptr(WM_IME_CONTROL), IMC_GETOPENSTATUS, 0) + logger.Debug("sendMessage", "ret", ret) + return ret != 0, nil +} + +func boolToUintptr(b bool) uintptr { + if b { + return 1 + } + return 0 +} + +func (_ *Ime) On(_ string, _ *struct{}) error { + <-connCh + logger.Debug("Ime.On requested") + hWnd, _, _ := getForegroundWindow.Call() + hIME, _, _ := immGetDefaultIMEWnd.Call(hWnd) + if hIME == 0 { + return fmt.Errorf("failed to get ime handle") + } + + sendMessage.Call(uintptr(hIME), uintptr(WM_IME_CONTROL), IMC_SETOPENSTATUS, 1) + + return nil +} + +func (_ *Ime) Off(_ string, _ *struct{}) error { + <-connCh + logger.Debug("Ime.Off requested") + hWnd, _, _ := getForegroundWindow.Call() + hIME, _, _ := immGetDefaultIMEWnd.Call(hWnd) + if hIME == 0 { + return fmt.Errorf("failed to get ime handle") + } + + sendMessage.Call(uintptr(hIME), uintptr(WM_IME_CONTROL), IMC_SETOPENSTATUS, 0) + + return nil +} + +func (_ *Ime) Toggle(_ string, _ *struct{}) error { + <-connCh + logger.Debug("Ime.Toggle requested") + enabled, err := isIMEEnabled() + if err != nil { + return nil + } + logger.Debug("Current IME status", "sutatus", boolToUintptr(enabled)) + hWnd, _, _ := getForegroundWindow.Call() + hIME, _, _ := immGetDefaultIMEWnd.Call(hWnd) + if hIME == 0 { + return fmt.Errorf("failed to get ime handle") + } + + sendMessage.Call(uintptr(hIME), uintptr(WM_IME_CONTROL), + IMC_SETOPENSTATUS, boolToUintptr(!enabled)) + enabled, err = isIMEEnabled() + if err != nil { + return nil + } + logger.Debug("Current IME status", "status", boolToUintptr(enabled)) + + return nil +} diff --git a/server/server.go b/server/server.go index a85275e..b94160c 100644 --- a/server/server.go +++ b/server/server.go @@ -74,4 +74,6 @@ func init() { rpc.Register(uri) clipboard := &Clipboard{} rpc.Register(clipboard) + ime := &Ime{} + rpc.Register(ime) }