diff --git a/Makefile b/Makefile index c8970ee..221f8b8 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ .PHONY: build build: - go build -compiler gccgo -gccgoflags "-lX11" shift-shift.go + CGO_ENABLED=1 go build shift-shift.go + +.PHONY: lint +lint: + golangci-lint run ./... diff --git a/README.md b/README.md index 721286d..22a2777 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,279 @@ -What is it and why? -=================== +## What is it and why? -Language layout switcher for Xorg and Wayland (Sway support) with -modifiers (Shift, Control and so on) as switcher keys. +*Do you want to use Shift/Ctrl/Alt as modifiers while also switching language +layouts? This tool is for you then.* -This way allows to use the same key as a modifier and as a layout switcher without conflicts. +A language layout switcher for Xorg and Wayland (including Sway and Hyprland) +that utilizes modifier keys (such as Shift and Control) as layout switchers. +This setup enables the same key to function both as a modifier and a layout +switcher without conflicts. -The utility implements two ideas: +The utility incorporates two key concepts: -1. You could use a key as a modifier when you HOLD it and as a key switcher when you TAP it. -2. Cyclic switching by a single key is a bad idea, better to use DEDICATED keys for each language group. +1. Use a key as a modifier when held and as a layout switcher when tapped. +2. Cyclic switching with a single key is inefficient; dedicated keys for each + language group are more effective. -It maybe not true if you use a bunch of languages of same time but it -is true for the most use cases with 2-3 langs. +This approach may not be necessary when using multiple languages simultaneously, +but it is beneficial for most cases involving 2-3 languages. Frequent keyboard +layout switching, common among non-English speakers, is easier with dedicated +keys than with key combinations. For example, old Soviet computers featured a +dedicated RUS/LAT key to toggle between Latin and Cyrillic. Some computers even +offered two separate keys: RUS and LAT! -If you are often switch keyboard layouts (it real use case -for those who speaking not only English) then dedicated keys are more easy -for typing than key combos. Old Soviet computers for example had -dedicated key RUS/LAT for switch between Latin and Cyrillic. +Unfortunately, modern English-oriented keyboards lack dedicated layout-switching +keys. Modifier keys are typically used only in combination with other keys. This +utility provides a solution: these keys can perform their original functions +while also being used to switch layouts. -Sadly in modern English-oriented keyboards there are no dedicated keys -for switching layouts. Modifier keys usually used only by holding with -other key. So it looks like a good compromise: you are still able to -use them for their original purposes but when you tapping them they -work as language layout switchers. +By default, the Left Shift key switches to layout 1 and the Right Shift key to +layout 2. You can customize this behavior using command-line options. The utility +supports up to 4 groups. -By default Left Shift swithches to group 1 and Right Shift to -group 2. You may change this behavior with command line options. -Up to 4 xkb groups supported. +Additionally, you can treat any input device as a keyboard using the `-match` +option, enabling simultaneous layout switching across multiple keyboards. The +utility listens to all connected devices. -Also you could try to treat any devices as keyboards with `-match` -option. It allows to switch group simultaneously on an arbitrary -number of connected keyboards. +The program listens to keypress events from the kernel (via evdev) and sends +them to Xorg or your window manager. Different WMs have their own methods for +keyboard switching, especially on Wayland. I have added support for Wayland +window managers I have used extensively. See the examples for all variants. -Install -======= +## Install -This is a Go program. You should need Go environment to build it from sources. +### Go way + +This is a Go program. You need a Go environment to build it from source. go get github.com/grafov/shift-shift@latest -`xlib-devel` libs should be installed. Check your distro. +`xlib-devel` lib should be installed. Check your distro. + +### Get sources + +1. Get repository and cd to it. +2. `make build` -Usage -===== +Prerequisites are the same: `xlib-devel` lib should be installed. Check your distro. + +## Usage ``` -$ shift-shift -h -Usage of shift-shift: +$ Usage of ./shift-shift: -1 string - key used for switching to 1st xkb group (default "LEFTSHIFT") + key used for switching to 1st xkb group (default "LEFTSHIFT") -2 string - key used for switching to 2nd xkb group (default "RIGHTSHIFT") + key used for switching to 2nd xkb group (default "RIGHTSHIFT") -3 string - key used for switching to 3rd xkb group + key used for switching to 3rd xkb group -4 string - key used for switching to 4th xkb group + key used for switching to 4th xkb group -double-keystroke - require pressing the same key twice to switch the layout + require pressing the same key twice to switch the layout -double-keystroke-timeout int - second keystroke timeout in milliseconds (default 500) + second keystroke timeout in milliseconds (default 500) -list - list all devices that found by evdev (not only keyboards) + list all devices that found by evdev (not only keyboards) + -list-hypr + list all keyboards recognized by Hyprland -list-sway - list all devices recognized by Sway (not only keyboards) + list all devices recognized by Sway (not only keyboards) -match string - regexp used to match keyboard device (default "keyboard") + regexp used to match input keyboard device as it listed by evdev (default "keyboard") + -match-wm string + optional regexp used to match device in WM, if not set evdev regexp used (default "keyboard") -print - print pressed keys for debug (verbose output) + print pressed keys for debug (verbose output) -quiet - be silent + be silent -scan-once - scan for keyboards only at startup (less power consumption) + scan for keyboards only at startup (less power consumption) -switcher string - select method of switching (possible values are "auto", "xkb", "sway") (default "auto") + select method of switching (possible values are "auto", "xkb", "sway", "hypr") (default "auto") ``` -On start the program tries to find devices where name contains "keyboard" string. The substring could be customized with `-match` option. [The syntax](https://pkg.go.dev/regexp/syntax) of regular expressions could be used. For example: +## Configuration + +### 1. Match keyboard in evdev output +First step is common for all environments. Just try to find your keyboard device. It may look like this: + ``` -$ shift-shift -match "(?i)ergodox|ergohaven" +$ shift-shift -list + +/dev/input/event3 PC Speaker +/dev/input/event4 HDA ATI HDMI HDMI/DP,pcm=3 +/dev/input/event6 HDA ATI HDMI HDMI/DP,pcm=8 +/dev/input/event8 HD-Audio Generic Rear Mic +/dev/input/event12 HD-Audio Generic Front Headphone +/dev/input/event20 ZSA Technology Labs ErgoDox EZ System Control +/dev/input/event21 ZSA Technology Labs ErgoDox EZ Consumer Control +/dev/input/event23 VIRPIL Controls/20210102 R-VPC Stick MT-50 +/dev/input/event7 HDA ATI HDMI HDMI/DP,pcm=9 +/dev/input/event18 ZSA Technology Labs ErgoDox EZ +/dev/input/event22 ZSA Technology Labs ErgoDox EZ Keyboard +/dev/input/event24 ATMEL/VIRPIL/190930 BRD Rudder V3 +/dev/input/event5 HDA ATI HDMI HDMI/DP,pcm=7 +/dev/input/event17 Kensington Kensington Expert Mouse +/dev/input/event19 ZSA Technology Labs ErgoDox EZ +... ``` -Check the list of evdev detected devices with: +And consists of any devices recognized by kernel on your machine. Just select any that looks like a keyboard :) + +**Note:** you need setup proper access for reading `/dev/input/*` +devices. As a fallback try to run with `sudo` or similar tool. + +You should add it `-match` key. You are free to use regular expression or just a substring for matching. Check [The +syntax of regexps](https://pkg.go.dev/regexp/syntax) for reference. ``` -$ shift-shift -list +-match="^ZSA.*Keyboard$" +``` + +Hint. You could match several keyboards at once. They will used when connected: + +``` +$ shift-shift -match "Ergodox|Ergohaven" ``` -**Wayland/Sway note.** In the same time `-match` option applied to the -list of Sway input devices. You could check them with: +Well, we've matched device with evdev. +### 2. Match switcher + +Next step is match it to a switcher. It +depends of your environment, see variants below. + +#### 2.1 WM or DE under XOrg + +``` +$ shift-shift -match="^ZSA.*Keyboard$" -switcher=xkb +``` + +And it will run switcher especially for XOrg. + +#### 2.2 Sway WM + +In most of cases you should just add "-switcher=sway" to command line and it will works. + +``` shell +$ shift-shift -match="^ZSA.*Keyboard$" -switcher=sway ``` + +You could list keyboards as they seen by Sway: + +``` shell $ shift-shift -list-sway + + { + "identifier": "12951:18804:ZSA_Technology_Labs_ErgoDox_EZ_Keyboard", + "name": "ZSA Technology Labs ErgoDox EZ Keyboard", + "type": "keyboard", + "repeat_delay": 350, + "repeat_rate": 50, + "xkb_layout_names": [ + "English (US)", + "Russian (typewriter)" + ], + "xkb_active_layout_index": 0, + "xkb_active_layout_name": "English (US)", + "libinput": { + "send_events": "enabled" + }, + "vendor": 12951, + "product": 18804 + }, + { + "identifier": "12951:18804:ZSA_Technology_Labs_ErgoDox_EZ_Consumer_Control", + "name": "ZSA Technology Labs ErgoDox EZ Consumer Control", +... ``` -**Note:** you need setup proper access for reading `/dev/input/*` -devices. As a fallback try to run with `sudo` or similar tool. +It will display your list of devices (but not only keyboards). + +Sometimes Sway may name the keyboard differently. In such cases, you can match +the keyboard using a regular expression similar to the evdev example above. +Then, add the result to the "-match-wm" key: + +``` +$ shift-shift -match="^ZSA.*Keyboard$" -switcher=sway -match-wm="^ZSA.*Keyboard$" +``` + +When "-match-wm" omited its value set to value of "-match". + +#### 2.3 Hyprland WM + +In most of cases you should just add "-switcher=hypr" to command line and it will works. + +``` shell +$ shift-shift -match="(?i)ergodox" -switcher=hypr +``` + +You could list keyboards as they seen by Hyprland: + +``` shell +$ shift-shift -list-hypr + +model: name:zsa-technology-labs-ergodox-ez layout:us,ru options:caps:none,misc:typo,lv3:capslock_switch keymap:English (US) +``` + +Naming in Hyprland differs from evdev. You can match the keyboard using a +regular expression, similar to evdev as described above. Then, add the result to +the "-match-wm" key: + +``` shell +$ shift-shift -match="^ZSA.*Keyboard$" -switcher=hypr -match-wm="^zsa-technology-labs-ergodox-ez$" +``` + +When "-match-wm" omited its value set to value of "-match". + +#### 3. Print for detected keyboards + +For debugging you could run with "-print" option. For example the output for Hyprland config above: + +``` +2025/01/30 01:12:28 use Hyprland switcher +2025/01/30 01:12:28 Hyprland keyboard matched zsa-technology-labs-ergodox-ez +2025/01/30 01:12:29 evdev keyboard found at /dev/input/event21: ZSA Technology Labs ErgoDox EZ Consumer Control +2025/01/30 01:12:29 evdev keyboard found at /dev/input/event22: ZSA Technology Labs ErgoDox EZ Keyboard + +``` + +#### 4. Keys for switching + +By default, the left Shift key is used for layout 0, and the right Shift key for +layout 1. You can use up to four layouts, naming them with the keys "-1" through +"-4". In the example below, the Ctrls key is used: + +``` shell +$ shift-shift -match="^ZSA.*Keyboard$" -switcher=hypr -match-wm="^zsa-technology-labs-ergodox-ez$" -1 LEFT_CTRL -2 RIGHT_CTRL -print +``` + +The output with "-print" when you press any of switch keys: + +``` +2025/01/30 01:12:29 ZSA Technology Labs ErgoDox EZ type:1 code:90 pressed +2025/01/30 01:12:29 ZSA Technology Labs ErgoDox EZ type:1 code:90 released +2025/01/30 01:12:29 ZSA Technology Labs ErgoDox EZ switches group to 1 +2025/01/30 01:12:29 switch hyprland kbd "zsa-technology-labs-ergodox-ez" to group 0 +2025/01/30 01:12:30 ZSA Technology Labs ErgoDox EZ type:1 code:91 pressed +2025/01/30 01:12:30 ZSA Technology Labs ErgoDox EZ type:1 code:91 released +2025/01/30 01:12:30 ZSA Technology Labs ErgoDox EZ switches group to 2 +``` + +For reference full list of key names recognized by evdev: + +``` shell +$ cat /usr/include/linux/input-event-codes.h +``` + +#### 5. Final setup + +Just remove "-print" once everything is working. Then, add the final command to +your window manager's autostart. + +$ shift-shift -match="^ZSA.*Keyboard$" -switcher=hypr -match-wm="^zsa-technology-labs-ergodox-ez$" -1 LEFT_CTRL -2 RIGHT_CTRL -print -Thanks -====== +## Thanks Thanks to people who contributed bugreports and improvements for `shift-shift`, especially to diff --git a/go.mod b/go.mod index 1520c65..28a1f30 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/grafov/shift-shift -go 1.20 +go 1.21 require github.com/grafov/evdev v1.0.0 + +require github.com/thiagokokada/hyprland-go v0.4.0 diff --git a/go.sum b/go.sum index 39c3a80..db936fe 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/grafov/evdev v1.0.0 h1:/rsOssITVhM7GPMyvlT34kiAOnvYzKJnKvuOqaMYW7c= github.com/grafov/evdev v1.0.0/go.mod h1:dcJfZnwr3VySHuphTV+Q0JcbP3AWR9W4WqbLeA3bG6U= +github.com/thiagokokada/hyprland-go v0.4.0 h1:SJQYVWYde240BjseQr7QRKRDCMdieKlaMO5RNWG2vh8= +github.com/thiagokokada/hyprland-go v0.4.0/go.mod h1:gUGbdxhD7QdvPpdEwsB09x0HzICPkhKAuCeY+LVx5YM= diff --git a/hyprland/helpers.go b/hyprland/helpers.go new file mode 100644 index 0000000..23dfb63 --- /dev/null +++ b/hyprland/helpers.go @@ -0,0 +1,70 @@ +package hyprland + +import ( + "fmt" + "os" + "strings" +) + +type keyboard struct { + Address string `json:"address"` + Name string `json:"name"` + Rules string `json:"rules"` + Model string `json:"model"` + Layout string `json:"layout"` + Variant string `json:"variant"` + Options string `json:"options"` + ActiveKeymap string `json:"active_keymap"` + Main bool `json:"main"` +} + +func PrintDevices() ([]string, error) { + inputs, err := getDevices() + if err != nil { + return nil, err + } + output := make([]string, len(inputs)) + for i, inp := range inputs { + if !inp.Main { + continue + } + output[i] = fmt.Sprintf("model:%s name:%s layout:%s options:%s keymap:%s\n", + inp.Model, inp.Name, inp.Layout, inp.Options, inp.ActiveKeymap) + } + return output, nil +} + +func getDevices() ([]keyboard, error) { + devs, err := hypr.Devices() + if err != nil { + return nil, err + } + kbds := make([]keyboard, len(devs.Keyboards)) + for i, v := range devs.Keyboards { + kbds[i] = keyboard{ + Address: v.Address, + Name: v.Name, + Rules: v.Rules, + Model: v.Model, + Layout: v.Layout, + Variant: v.Variant, + Options: v.Options, + ActiveKeymap: v.ActiveKeymap, + Main: v.Main, + } + } + return kbds, nil +} + +func CheckAvailability() bool { + if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "" { + return true + } + checkVars := []string{"DESKTOP_SESSION", "XDG_CURRENT_DESKTOP", "XDG_SESSION_DESKTOP"} + for _, v := range checkVars { + if strings.ToLower(os.Getenv(v)) == "sway" { + return true + } + } + return false +} diff --git a/hyprland/wayland-hyprland.go b/hyprland/wayland-hyprland.go new file mode 100644 index 0000000..99413d4 --- /dev/null +++ b/hyprland/wayland-hyprland.go @@ -0,0 +1,80 @@ +package hyprland + +import ( + "fmt" + "log" + "os" + "regexp" + "strconv" + "sync" + "time" + + "github.com/thiagokokada/hyprland-go" +) + +// FIXME use NewClient() and move into Hyprland struct +var hypr = hyprland.MustClient() + +type Hyprland struct { + re *regexp.Regexp + sleep time.Duration + once bool + debug bool + + m sync.RWMutex + keyboards []string +} + +func New(re *regexp.Regexp, scanPeriod time.Duration, scanOnce bool, debug bool) *Hyprland { + return &Hyprland{re: re, sleep: scanPeriod, once: scanOnce, debug: debug} +} + +func (h *Hyprland) Init() error { + go h.matchKeyboards(h.debug) + return nil +} + +func (h *Hyprland) Switch(id int) { + h.m.RLock() + for _, kbd := range h.keyboards { + if h.debug { + log.Printf("switch hyprland kbd \"%s\" to group %d", kbd, id-1) + } + resp, err := hypr.SwitchXkbLayout(kbd, strconv.Itoa(id-1)) + if err != nil { + fmt.Fprintf(os.Stderr, "response: %v, error: %s", resp, err) + } + } + h.m.RUnlock() +} + +func (h *Hyprland) Name() string { + return "Hyprland" +} + +func (h *Hyprland) Close() {} + +func (h *Hyprland) matchKeyboards(debug bool) { + for { + inputs, err := getDevices() + if err != nil { + fmt.Fprintf(os.Stderr, "can't get input devices from Hyprland: %s", err) + } + h.m.Lock() + h.keyboards = nil + for _, in := range inputs { + if h.re.MatchString(in.Name) && in.Main { + if debug { + log.Printf("Hyprland keyboard matched %s", in.Name) + } + + h.keyboards = append(h.keyboards, in.Name) + } + } + h.m.Unlock() + if h.once { + return + } + time.Sleep(h.sleep) + } +} diff --git a/shift-shift.go b/shift-shift.go index b943dd7..2f61836 100644 --- a/shift-shift.go +++ b/shift-shift.go @@ -10,6 +10,7 @@ import ( "regexp" "time" + "github.com/grafov/shift-shift/hyprland" "github.com/grafov/shift-shift/sway" "github.com/grafov/shift-shift/xkb" @@ -35,9 +36,11 @@ func main() { var err error listDevices := flag.Bool("list", false, `list all devices that found by evdev (not only keyboards)`) listSwayDevices := flag.Bool("list-sway", false, `list all devices recognized by Sway (not only keyboards)`) + listHyprlandDevices := flag.Bool("list-hypr", false, `list all keyboards recognized by Hyprland`) printMode := flag.Bool("print", false, `print pressed keys for debug (verbose output)`) quietMode := flag.Bool("quiet", false, `be silent`) - kbdRegex := flag.String("match", "keyboard", `regexp used to match keyboard device`) + kbdRegex := flag.String("match", "keyboard", `regexp used to match input keyboard device as it listed by evdev`) + wmRegex := flag.String("match-wm", "keyboard", `optional regexp used to match device in WM, if not set evdev regexp used`) keysym1 := flag.String("1", "LEFTSHIFT", `key used for switching to 1st xkb group`) keysym2 := flag.String("2", "RIGHTSHIFT", `key used for switching to 2nd xkb group`) keysym3 := flag.String("3", "", `key used for switching to 3rd xkb group`) @@ -45,10 +48,10 @@ func main() { scanOnce := flag.Bool("scan-once", false, `scan for keyboards only at startup (less power consumption)`) dblKeystroke := flag.Bool("double-keystroke", false, `require pressing the same key twice to switch the layout`) dblKeyTimeout := flag.Int("double-keystroke-timeout", 500, `second keystroke timeout in milliseconds`) - switchMethod := flag.String("switcher", "auto", `select method of switching (possible values are "auto", "xkb", "sway")`) + switchMethod := flag.String("switcher", "auto", `select method of switching (possible values are "auto", "xkb", "sway", "hypr")`) flag.Parse() - terminate := make(chan os.Signal) + terminate := make(chan os.Signal, 1) signal.Notify(terminate, os.Interrupt) if *listDevices { @@ -58,19 +61,30 @@ func main() { return } if *listSwayDevices { - var list []sway.SwayInput - if list, err = sway.GetInputDevices(); err != nil { + var list []string + if list, err = sway.PrintDevices(); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } for _, inp := range list { - fmt.Printf("%s type:%s name:%s layout:%s group:%d\n", - inp.Identifier, inp.Type, inp.Name, inp.XkbActiveLayoutName, inp.XkbActiveLayoutIndex) + fmt.Println(inp) } return } - switches := make([]uint16, 4, 4) + if *listHyprlandDevices { + var list []string + if list, err = hyprland.PrintDevices(); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + for _, inp := range list { + fmt.Println(inp) + } + return + } + + switches := make([]uint16, 4) for i, k := range []string{*keysym1, *keysym2, *keysym3, *keysym4} { if k == "" { continue @@ -82,11 +96,25 @@ func main() { } } - matchDevs, err := regexp.Compile(*kbdRegex) + matchEvdevKbds, err := regexp.Compile(*kbdRegex) if err != nil { fmt.Fprintf( os.Stderr, - "unable to compile regexp for matching devices: %s\n", + "unable to compile regexp for matching devices of evdev: %s\n", + err, + ) + os.Exit(1) + } + + if wmRegex == nil { + wmRegex = kbdRegex + } + + matchWMKbds, err := regexp.Compile(*wmRegex) + if err != nil { + fmt.Fprintf( + os.Stderr, + "unable to compile regexp for matching devices in WM: %s\n", err, ) os.Exit(1) @@ -94,16 +122,21 @@ func main() { var sw switcher switch *switchMethod { + case "hypr": + sw = hyprland.New(matchWMKbds, scanPeriod, *scanOnce, *printMode) case "sway": - sw = sway.New(matchDevs, scanPeriod, *scanOnce, *printMode) + sw = sway.New(matchWMKbds, scanPeriod, *scanOnce, *printMode) case "xkb": sw = xkb.New() case "auto": fallthrough default: - if sway.CheckAvailability() { - sw = sway.New(matchDevs, scanPeriod, *scanOnce, *printMode) - } else { + switch { + case hyprland.CheckAvailability(): + sw = hyprland.New(matchWMKbds, scanPeriod, *scanOnce, *printMode) + case sway.CheckAvailability(): + sw = sway.New(matchWMKbds, scanPeriod, *scanOnce, *printMode) + default: sw = xkb.New() } } @@ -123,7 +156,7 @@ func main() { go listenKeyboards(sw, switches, *printMode, *quietMode, - matchDevs, *scanOnce, + matchEvdevKbds, *scanOnce, *dblKeystroke, *dblKeyTimeout, ) @@ -172,7 +205,7 @@ func scanDevices(mbox chan message, deviceMatch *regexp.Regexp, quietMode bool, if deviceMatch.MatchString(device.Name) { if _, ok := keyboards[devicePath]; !ok { if !quietMode { - log.Printf("keyboard found at %s: %s", devicePath, device.Name) + log.Printf("evdev keyboard found at %s: %s", devicePath, device.Name) } if mbox != nil { keyboards[devicePath] = device @@ -216,65 +249,62 @@ func listenKeyboards( go scanDevices(inbox, deviceMatch, quietMode, scanOnce) var prevKey evdev.InputEvent - for { - select { - case msg := <-inbox: - for _, ev := range msg.Events { - if ev.Type != evdev.EV_KEY { - continue - } - if prevKey.Code == ev.Code && prevKey.Value == ev.Value && prevKey.Type == ev.Type { - continue + for msg := range inbox { + for _, ev := range msg.Events { + if ev.Type != evdev.EV_KEY { + continue + } + if prevKey.Code == ev.Code && prevKey.Value == ev.Value && prevKey.Type == ev.Type { + continue + } + prevKey.Type = ev.Type + prevKey.Code = ev.Code + prevKey.Value = ev.Value + if printMode { + var pv string + switch ev.Value { + case 0: + pv = "released" + case 1: + pv = "pressed" + case 2: + pv = "hold" + default: + pv = "undefined status" } - prevKey.Type = ev.Type - prevKey.Code = ev.Code - prevKey.Value = ev.Value - if printMode { - var pv string - switch ev.Value { - case 0: - pv = "released" - case 1: - pv = "pressed" - case 2: - pv = "hold" - default: - pv = "undefined status" + log.Printf("%s type:%v code:%v %s", msg.Device.Name, ev.Type, ev.Code, pv) + } + switch ev.Value { + case 1: // key down + useGroup = 0 + for i, k := range switches { + if ev.Code != k { + continue } - log.Printf("%s type:%v code:%v %s", msg.Device.Name, ev.Type, ev.Code, pv) - } - switch ev.Value { - case 1: // key down - useGroup = 0 - for i, k := range switches { - if ev.Code != k { - continue - } - ready := true - if dblKeystroke { - t, ready = checkTimeout(t, dblKeyTimeout) - } - if ready { - useGroup = i + 1 - } + ready := true + if dblKeystroke { + t, ready = checkTimeout(t, dblKeyTimeout) } - case 0: // key up - if useGroup == 0 { - break + if ready { + useGroup = i + 1 } - for i, k := range switches { - if ev.Code != k { - continue - } - if useGroup == i+1 { - if printMode { - log.Printf("%s switches group to %d", msg.Device.Name, useGroup) - } - sw.Switch(useGroup) + } + case 0: // key up + if useGroup == 0 { + break + } + for i, k := range switches { + if ev.Code != k { + continue + } + if useGroup == i+1 { + if printMode { + log.Printf("%s switches group to %d", msg.Device.Name, useGroup) } + sw.Switch(useGroup) } - useGroup = 0 } + useGroup = 0 } } } diff --git a/sway/helpers.go b/sway/helpers.go index 0dbe5e8..3811864 100644 --- a/sway/helpers.go +++ b/sway/helpers.go @@ -2,6 +2,7 @@ package sway import ( "encoding/json" + "fmt" "os" "strings" ) @@ -19,7 +20,7 @@ func CheckAvailability() bool { return false } -type SwayInput struct { +type device struct { Identifier string `json:"identifier"` Name string `json:"name"` Type string `json:"type"` @@ -27,14 +28,27 @@ type SwayInput struct { XkbActiveLayoutName string `json:"xkb_active_layout_name"` } -func GetInputDevices() ([]SwayInput, error) { +func getDevices() ([]device, error) { out, err := swayexec("-t", "get_inputs") if err != nil { return nil, err } - var inputs []SwayInput + var inputs []device if err = json.Unmarshal(out, &inputs); err != nil { return nil, err } return inputs, nil } + +func PrintDevices() ([]string, error) { + inputs, err := getDevices() + if err != nil { + return nil, err + } + output := make([]string, len(inputs)) + for i, inp := range inputs { + output[i] = fmt.Sprintf("%s type:%s name:%s layout:%s group:%d\n", + inp.Identifier, inp.Type, inp.Name, inp.XkbActiveLayoutName, inp.XkbActiveLayoutIndex) + } + return output, err +} diff --git a/sway/wayland-sway.go b/sway/wayland-sway.go index dd031a3..57cfbd9 100644 --- a/sway/wayland-sway.go +++ b/sway/wayland-sway.go @@ -32,7 +32,7 @@ func (s *Sway) Init() error { } func (*Sway) Name() string { - return "sway" + return "Sway" } func (s *Sway) Switch(idx int) { @@ -52,7 +52,7 @@ func (*Sway) Close() {} func (s *Sway) matchOnlyKeyboards() { for { - inputs, err := GetInputDevices() + inputs, err := getDevices() if err != nil { fmt.Fprintf(os.Stderr, "can't get input devices from Sway: %s", err) } diff --git a/xkb/xkb.go b/xkb/xkb.go index 1d35509..9cb8b3a 100644 --- a/xkb/xkb.go +++ b/xkb/xkb.go @@ -22,7 +22,7 @@ func New() *Xkb { } func (*Xkb) Name() string { - return "xkb" + return "XKB" } func (x *Xkb) Init() error {