Skip to content

Commit

Permalink
improvements to the display command
Browse files Browse the repository at this point in the history
- display can now output left, right, and primary monitors names, on
demand
- refactored X display parsing into it's own struct with init, getter,
and setter methods.
- polybar and display commands now use new displays struct
- fixed bug where display run was not able to find the current settings
file

Note to self: I should really add tests.
  • Loading branch information
patrick-motard committed Sep 22, 2019
1 parent c902f50 commit 396b096
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 112 deletions.
121 changes: 121 additions & 0 deletions cmd/display.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package cmd

import (
"sort"

"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/randr"
"github.com/BurntSushi/xgb/xproto"
)

type display struct {
name string // example: DP-4 or HDMI-1
// Position is where the display is relative to other displays on the screen.
// Screens are comprised of one or more displays.
xposition int16 // the x coordinate of the display on the screen
yposition int16 // the y coordinate of the display on the screen
xres uint16 // The ideal x resolution.
yres uint16 // The idea y resolution.
primary bool // Whether or not the display is the primary (main) display.
active bool
}
type displays struct {
displays []display
left display
right display
primary display
}

func (ds displays) getLeft() display {
ds.get()
return ds.left
}
func (ds displays) getRight() display {
ds.get()
return ds.right
}
func (ds displays) getPrimary() display {
ds.get()
return ds.primary
}

// gets current displays, sets them on the struct, and returns them
func (ds *displays) get() []display {
if len(ds.displays) > 0 {
return ds.displays
}
// connect to X server
X, _ := xgb.NewConn()
err := randr.Init(X)
if err != nil {
log.Fatal(err)
}

// get root node
root := xproto.Setup(X).DefaultScreen(X).Root
// get the resources of the screen
resources, err := randr.GetScreenResources(X, root).Reply()
if err != nil {
log.Fatal(err)
}
// get the primary output
primaryOutput, _ := randr.GetOutputPrimary(X, root).Reply()

// go through the connected outputs and get their position and resolution
for _, output := range resources.Outputs {
info, err := randr.GetOutputInfo(X, output, 0).Reply()
if err != nil {
log.Fatal(err)
}
if info.Connection == randr.ConnectionConnected {
d := display{
name: string(info.Name),
}
crtc, err := randr.GetCrtcInfo(X, info.Crtc, 0).Reply()
if err != nil {
// log.Fatal("Failed to get CRTC info", err)
// "BadCrtc" happens when attempting to get
// a crtc for an output is disabled (inactive).
// TODO: figure out a better way to identify active vs inactive
d.active = false
} else {
d.active = true
d.xposition = crtc.X
d.yposition = crtc.Y
}

if output == primaryOutput.Output {
d.primary = true
} else {
d.primary = false
}
bestMode := info.Modes[0]
for _, mode := range resources.Modes {
if mode.Id == uint32(bestMode) {
d.xres = mode.Width
d.yres = mode.Height
}
}

ds.displays = append(ds.displays, d)
}
}
// order the displays by their position, left to right.
sort.Slice(ds.displays, func(i, j int) bool {
return ds.displays[i].xposition < ds.displays[j].xposition
})
for i, d := range ds.get() {
// skip inactive monitors
if !d.active {
continue
}
if d.primary {
ds.primary = d
} else if i == 0 {
ds.left = d
} else if i == 1 || i == 2 {
ds.right = d
}
}
return ds.displays
}
18 changes: 17 additions & 1 deletion cmd/displays.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

var _leftDisplay, _rightDisplay, _primaryDisplay bool
var displaysCmd = &cobra.Command{
Use: "displays",
Short: "All commands for interacting with your displays",
Expand All @@ -20,11 +23,24 @@ then call this script to apply it to your system.
Sometimes when you re-plugin displays to your graphics card, it can
invalidate your layout made with aRandR. It's a real annoyance that I don't have a
fix for right now. You will have to recreate the layout and re-run 'dot screen select'`,
fix for right now. You will have to recreate the layout and re-run 'dot screen select --rofi'`,
Run: func(cmd *cobra.Command, args []string) {
ds := displays{}
if _leftDisplay {
fmt.Println(ds.getLeft().name)
}
if _rightDisplay {
fmt.Println(ds.getRight().name)
}
if _primaryDisplay {
fmt.Println(ds.getPrimary().name)
}
},
}

func init() {
rootCmd.AddCommand(displaysCmd)
displaysCmd.Flags().BoolVarP(&_leftDisplay, "get-left", "l", false, "Get the name of your left display.")
displaysCmd.Flags().BoolVarP(&_rightDisplay, "get-right", "r", false, "Get the name of your right display.")
displaysCmd.Flags().BoolVarP(&_primaryDisplay, "get-primary", "p", false, "Get the name of your primary display.")
}
45 changes: 28 additions & 17 deletions cmd/displaysSelect.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@ package cmd
import (
// "errors"
"fmt"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
"os/exec"
"strings"

"github.com/manifoldco/promptui"
"github.com/patrick-motard/rofigo"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var _rofi bool

// selectCmd represents the select command
var displaysSelectCmd = &cobra.Command{
Use: "select",
Short: "Interactively select an RandR script from list apply it to your system.",
Long: `Example:
Long: `Interactively select an RandR script from list apply it to your system.
You can select the display via rofi by setting the --rofi/-r flag.
Example (in command line):
dot screen select
Use the arrow keys to navigate: ↓ ↑ → ←
? Pick one:
Expand All @@ -32,23 +39,26 @@ Dot will remember what you chose, even if you log out or reboot.
`,
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 0 {
fmt.Println("No arguments allowed for this command, exiting.")
os.Exit(1)
}
files, err := DisplaysLocation()
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
prompt := promptui.Select{
Label: "Pick one",
Items: files,
}
_, selection, err := prompt.Run()
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
var selection string
if _rofi == true {
v := rofigo.New("xrandr script", files...)
v.Show()
selection = v.Selection
} else {
prompt := promptui.Select{
Label: "Pick one",
Items: files,
}
_, selection, err = prompt.Run()
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
runErr := RunDisplaysScript(selection)
if runErr != nil {
Expand All @@ -66,14 +76,15 @@ Dot will remember what you chose, even if you log out or reboot.

func RunDisplaysScript(scriptName string) error {
location := viper.GetString("displays.location")
fullPath := strings.Join([]string{location, scriptName}, "/")
fullPath := strings.Join([]string{Home, "/", location, scriptName}, "/")
arandrCmd := exec.Command("/bin/sh", fullPath)
out, err := arandrCmd.CombinedOutput()
fmt.Println(string(out))
return err
}
func init() {
displaysCmd.AddCommand(displaysSelectCmd)
displaysSelectCmd.Flags().BoolVarP(&_rofi, "rofi", "r", false, "Use rofi to select display configuration.")

// Here you will define your flags and configuration settings.

Expand Down
106 changes: 12 additions & 94 deletions cmd/polybar.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Copyright © 2018 Patrick Motard <motard19@gmail.com>
// TODO: add i3wm dimensions to bar settings

package cmd

Expand All @@ -12,32 +11,16 @@ import (
"os"
"os/exec"
"regexp"
"sort"
"strings"
"sync"

"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/randr"
"github.com/BurntSushi/xgb/xproto"
"github.com/patrick-motard/rofigo"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.i3wm.org/i3"
// "time"
)

type display struct {
name string // example: DP-4 or HDMI-1
// Position is where the display is relative to other displays on the screen.
// Screens are comprised of one or more displays.
xposition int16 // the x coordinate of the display on the screen
yposition int16 // the y coordinate of the display on the screen
xres uint16 // The ideal x resolution.
yres uint16 // The idea y resolution.
primary bool // Whether or not the display is the primary (main) display.
active bool
}

var (
_theme string
_list bool
Expand All @@ -62,7 +45,7 @@ var polybarCmd = &cobra.Command{
os.Exit(0)
}
if _select == true {
v := rofigo.New("Select Polybar theme:", InstalledPolybarThemes...)
v := rofigo.New("Select Polybar theme", InstalledPolybarThemes...)
v.Show()
log.Infof("You selected: %s", v.Selection)

Expand Down Expand Up @@ -142,67 +125,8 @@ func validateTheme() bool {
}

func main() {
// connect to X server
X, _ := xgb.NewConn()
err := randr.Init(X)
if err != nil {
log.Fatal(err)
}

// get root node
root := xproto.Setup(X).DefaultScreen(X).Root
// get the resources of the screen
resources, err := randr.GetScreenResources(X, root).Reply()
if err != nil {
log.Fatal(err)
}
// get the primary output
primaryOutput, _ := randr.GetOutputPrimary(X, root).Reply()

var displays []display
// go through the connected outputs and get their position and resolution
for _, output := range resources.Outputs {
info, err := randr.GetOutputInfo(X, output, 0).Reply()
if err != nil {
log.Fatal(err)
}
if info.Connection == randr.ConnectionConnected {
d := display{
name: string(info.Name),
}
crtc, err := randr.GetCrtcInfo(X, info.Crtc, 0).Reply()
if err != nil {
// log.Fatal("Failed to get CRTC info", err)
// "BadCrtc" happens when attempting to get
// a crtc for an output is disabled (inactive).
// TODO: figure out a better way to identify active vs inactive
d.active = false
} else {
d.active = true
d.xposition = crtc.X
d.yposition = crtc.Y
}

if output == primaryOutput.Output {
d.primary = true
} else {
d.primary = false
}
bestMode := info.Modes[0]
for _, mode := range resources.Modes {
if mode.Id == uint32(bestMode) {
d.xres = mode.Width
d.yres = mode.Height
}
}
displays = append(displays, d)
}
}

// order the displays by their position, left to right.
sort.Slice(displays, func(i, j int) bool {
return displays[i].xposition < displays[j].xposition
})
ds := displays{}

// kill all polybar sessions polybar
cmd := exec.Command("sh", "-c", "killall -q polybar")
Expand All @@ -217,22 +141,16 @@ func main() {
// create the env vars we'll hand to polybar
// polybar needs to know the theme, and what the left, right and main monitor are
var polybarEnvVars []string
for i, d := range displays {
// skip inactive monitors
if !d.active {
continue
}
if d.primary {
s := fmt.Sprintf("MONITOR_MAIN=%s", d.name)
polybarEnvVars = append(polybarEnvVars, s)
} else if i == 0 {
s := fmt.Sprintf("MONITOR_LEFT=%s", d.name)
polybarEnvVars = append(polybarEnvVars, s)
} else if i == 1 || i == 2 {
s := fmt.Sprintf("MONITOR_RIGHT=%s", d.name)
polybarEnvVars = append(polybarEnvVars, s)
}
}

s := fmt.Sprintf("MONITOR_MAIN=%s", ds.getPrimary().name)
polybarEnvVars = append(polybarEnvVars, s)

s = fmt.Sprintf("MONITOR_LEFT=%s", ds.getLeft().name)
polybarEnvVars = append(polybarEnvVars, s)

s = fmt.Sprintf("MONITOR_RIGHT=%s", ds.getRight().name)
polybarEnvVars = append(polybarEnvVars, s)

// add the theme to the environment
t := fmt.Sprintf("polybar_theme=%s", FullThemePath)
log.Infoln(t)
Expand Down

0 comments on commit 396b096

Please sign in to comment.