Last active
April 6, 2021 00:43
-
-
Save pgaskin/16da4073fe27d34297bad99af1dad2bf to your computer and use it in GitHub Desktop.
A port of github.com/florentc/xob to Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package xob | |
| import ( | |
| "fmt" | |
| "github.com/BurntSushi/xgb" | |
| "github.com/BurntSushi/xgb/xproto" | |
| ) | |
| // ported from github.com/florentc/xob@d6ca69d6a45a9c1ac1c99e00357d0df32f956f19 (GPLv3) | |
| type OverflowMode int | |
| const ( | |
| OverflowModeHidden OverflowMode = iota | |
| OverflowModeProportional | |
| ) | |
| type Orientation int | |
| const ( | |
| OrientationHorizontal Orientation = iota | |
| OrientationVertical | |
| ) | |
| type ShowMode int | |
| const ( | |
| ShowModeNormal ShowMode = iota | |
| ShowModeAlternative | |
| ) | |
| type Bar struct { | |
| x xContext | |
| color colorContext | |
| geometry geometryContext | |
| } | |
| type colorspec struct { | |
| FG string | |
| BG string | |
| Border string | |
| // alpha is not used | |
| } | |
| type dim struct { | |
| Rel float64 | |
| Abs int | |
| } | |
| type xContext struct { | |
| Display *xgb.Conn | |
| Screen *xproto.ScreenInfo | |
| Window xproto.Window | |
| Mapped bool | |
| GC []xproto.Gcontext | |
| } | |
| type gcColorset struct { | |
| FG xproto.Gcontext | |
| BG xproto.Gcontext | |
| Border xproto.Gcontext | |
| } | |
| type colorContext struct { | |
| Normal gcColorset | |
| Overflow gcColorset | |
| Alt gcColorset | |
| AltOverflow gcColorset | |
| } | |
| type geometryContext struct { | |
| Outline int | |
| Border int | |
| Padding int | |
| Length int | |
| Thickness int | |
| Orientation Orientation | |
| } | |
| func (g geometryContext) SizeX() int { | |
| if g.Orientation == OrientationHorizontal { | |
| return g.Length | |
| } | |
| return g.Thickness | |
| } | |
| func (g geometryContext) SizeY() int { | |
| if g.Orientation == OrientationHorizontal { | |
| return g.Thickness | |
| } | |
| return g.Length | |
| } | |
| func NewBar(conf style) (*Bar, error) { | |
| var err error | |
| var dc Bar | |
| if dc.x.Display, err = xgb.NewConn(); err != nil { | |
| return nil, err | |
| } | |
| dc.x.Screen = xproto.Setup(dc.x.Display).DefaultScreen(dc.x.Display) | |
| dc.geometry.Outline = conf.Outline | |
| dc.geometry.Border = conf.Border | |
| dc.geometry.Padding = conf.Padding | |
| dc.geometry.Thickness = conf.Thickness | |
| dc.geometry.Orientation = conf.Orientation | |
| fatLayer := dc.geometry.Padding + dc.geometry.Border + dc.geometry.Outline | |
| var availableLength int | |
| if dc.geometry.Orientation == OrientationHorizontal { | |
| availableLength = int(dc.x.Screen.WidthInPixels) | |
| } else { | |
| availableLength = int(dc.x.Screen.HeightInPixels) | |
| } | |
| dc.geometry.Length = clamp( | |
| int(float64(availableLength)*conf.Length.Rel)+conf.Length.Abs, | |
| 0, | |
| availableLength-2*fatLayer, | |
| ) | |
| topLeftX := clamp( | |
| int(float64(dc.x.Screen.WidthInPixels)*conf.X.Rel)-(dc.geometry.SizeX()+2*fatLayer)/2, | |
| 0, | |
| int(dc.x.Screen.WidthInPixels)-(dc.geometry.SizeX()+2*fatLayer), | |
| ) + conf.X.Abs | |
| topLeftY := clamp( | |
| int(float64(dc.x.Screen.HeightInPixels)*conf.Y.Rel)-(dc.geometry.SizeY()+2*fatLayer)/2, | |
| 0, | |
| int(dc.x.Screen.HeightInPixels)-(dc.geometry.SizeY()+2*fatLayer), | |
| ) + conf.Y.Abs | |
| if dc.x.Window, err = xproto.NewWindowId(dc.x.Display); err != nil { | |
| dc.Close() | |
| return nil, err | |
| } | |
| if err = xproto.CreateWindowChecked( | |
| dc.x.Display, | |
| dc.x.Screen.RootDepth, dc.x.Window, dc.x.Screen.Root, | |
| int16(topLeftX), int16(topLeftY), | |
| uint16(dc.geometry.SizeX()+2*fatLayer), uint16(dc.geometry.SizeY()+2*fatLayer), | |
| 0, | |
| xproto.WindowClassInputOutput, dc.x.Screen.RootVisual, | |
| xproto.CwBackPixel|xproto.CwBorderPixel|xproto.CwOverrideRedirect|xproto.CwColormap, []uint32{dc.x.Screen.BlackPixel, dc.x.Screen.BlackPixel, 1, uint32(dc.x.Screen.DefaultColormap)}, | |
| ).Check(); err != nil { | |
| dc.Close() | |
| return nil, err | |
| } | |
| if err = xproto.ChangePropertyChecked(dc.x.Display, xproto.PropModeReplace, dc.x.Window, xproto.AtomWmName, xproto.AtomString, 8, 3, []byte{'x', 'o', 'b'}).Check(); err != nil { | |
| dc.Close() | |
| return nil, err | |
| } | |
| if err = xproto.ChangePropertyChecked(dc.x.Display, xproto.PropModeReplace, dc.x.Window, xproto.AtomWmClass, xproto.AtomString, 8, 3, []byte{'x', 'o', 'b'}).Check(); err != nil { | |
| dc.Close() | |
| return nil, err | |
| } | |
| // custom (not in original xob): also make it always-on-top | |
| { | |
| ts, err := xproto.InternAtom(dc.x.Display, false, uint16(len("_NET_WM_STATE")), "_NET_WM_STATE").Reply() | |
| if err != nil { | |
| return nil, err | |
| } | |
| tsa, err := xproto.InternAtom(dc.x.Display, false, uint16(len("_NET_WM_STATE_ABOVE")), "_NET_WM_STATE_ABOVE").Reply() | |
| if err != nil { | |
| return nil, err | |
| } | |
| if err := xproto.SendEventChecked(dc.x.Display, false, dc.x.Screen.Root, xproto.EventMaskSubstructureNotify|xproto.EventMaskSubstructureRedirect, string(xproto.ClientMessageEvent{ | |
| Type: ts.Atom, | |
| Window: dc.x.Window, | |
| Format: 32, | |
| Data: xproto.ClientMessageDataUnionData32New([]uint32{ | |
| 1, // _NET_WM_STATE_ADD | |
| uint32(tsa.Atom), | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| }), | |
| }.Bytes())).Check(); err != nil { | |
| return nil, err | |
| } | |
| } | |
| for _, v := range []struct { | |
| x *gcColorset | |
| y *colorspec | |
| }{ | |
| {&dc.color.Normal, &conf.Color.Normal}, | |
| {&dc.color.Overflow, &conf.Color.Overflow}, | |
| {&dc.color.Alt, &conf.Color.Alt}, | |
| {&dc.color.AltOverflow, &conf.Color.AltOverflow}, | |
| } { | |
| if v.x.FG, err = gcFromString(dc.x, v.y.FG); err != nil { | |
| dc.Close() | |
| return nil, err | |
| } else { | |
| dc.x.GC = append(dc.x.GC, v.x.FG) | |
| } | |
| if v.x.BG, err = gcFromString(dc.x, v.y.BG); err != nil { | |
| dc.Close() | |
| return nil, err | |
| } else { | |
| dc.x.GC = append(dc.x.GC, v.x.BG) | |
| } | |
| if v.x.Border, err = gcFromString(dc.x, v.y.Border); err != nil { | |
| dc.Close() | |
| return nil, err | |
| } else { | |
| dc.x.GC = append(dc.x.GC, v.x.Border) | |
| } | |
| } | |
| return &dc, nil | |
| } | |
| func (dc *Bar) Show(value int, cap int, overflowMode OverflowMode, showMode ShowMode) error { | |
| if !dc.x.Mapped { | |
| if err := xproto.MapWindowChecked(dc.x.Display, dc.x.Window).Check(); err != nil { | |
| return err | |
| } | |
| dc.x.Mapped = true | |
| if err := xproto.ConfigureWindowChecked(dc.x.Display, dc.x.Window, xproto.ConfigWindowStackMode, []uint32{xproto.StackModeAbove}).Check(); err != nil { | |
| return err | |
| } | |
| } | |
| var colorset, colorsetOverflowProportional gcColorset | |
| switch showMode { | |
| case ShowModeNormal: | |
| colorsetOverflowProportional = dc.color.Normal | |
| if value <= cap { | |
| colorset = dc.color.Normal | |
| } else { | |
| colorset = dc.color.Overflow | |
| } | |
| case ShowModeAlternative: | |
| colorsetOverflowProportional = dc.color.Alt | |
| if value <= cap { | |
| colorset = dc.color.Alt | |
| } else { | |
| colorset = dc.color.AltOverflow | |
| } | |
| } | |
| if err := drawEmpty(dc.x, dc.geometry, colorset); err != nil { | |
| return err | |
| } | |
| if err := drawContent(dc.x, dc.geometry, clamp(value, 0, cap)*dc.geometry.Length/cap, colorset.FG); err != nil { | |
| return err | |
| } | |
| if value > cap && overflowMode == OverflowModeProportional && cap*dc.geometry.Length/value > dc.geometry.Padding { | |
| if err := drawContent(dc.x, dc.geometry, cap*dc.geometry.Length/value, colorsetOverflowProportional.FG); err != nil { | |
| return err | |
| } | |
| if err := drawSeparator(dc.x, dc.geometry, cap*dc.geometry.Length/value, colorsetOverflowProportional.BG); err != nil { | |
| return err | |
| } | |
| } | |
| dc.x.Display.Sync() | |
| return nil | |
| } | |
| func (dc *Bar) Hide() error { | |
| if dc.x.Display == nil { | |
| return nil | |
| } | |
| if dc.x.Window != 0 { | |
| if err := xproto.UnmapWindowChecked(dc.x.Display, dc.x.Window).Check(); err != nil { | |
| return err | |
| } | |
| } | |
| dc.x.Mapped = false | |
| return nil | |
| } | |
| func (dc *Bar) Close() { | |
| if dc.x.Display == nil { | |
| return | |
| } | |
| if dc.x.Window != 0 { | |
| _ = dc.Hide() | |
| _ = xproto.DestroyWindowChecked(dc.x.Display, dc.x.Window).Check() | |
| } | |
| for _, gc := range dc.x.GC { | |
| _ = xproto.FreeGCChecked(dc.x.Display, gc).Check() | |
| } | |
| dc.x.Display.Close() | |
| } | |
| func clamp(value, min, max int) int { | |
| if value < min { | |
| return min | |
| } | |
| if value > max { | |
| return max | |
| } | |
| return value | |
| } | |
| func gcFromString(x xContext, color string) (xproto.Gcontext, error) { | |
| var pixel uint32 | |
| if len(color) != 0 && color[0] == '#' { | |
| var cr, cg, cb uint8 | |
| switch len(color) { | |
| case 7: | |
| if _, err := fmt.Sscanf(color, "#%02x%02x%02x", &cr, &cg, &cb); err != nil { | |
| return 0, fmt.Errorf("invalid hex color %q: %w", color, err) | |
| } | |
| case 4: | |
| if _, err := fmt.Sscanf(color, "#%02x%02x%02x", &cr, &cg, &cb); err != nil { | |
| return 0, fmt.Errorf("invalid hex color %q: %w", color, err) | |
| } | |
| cr *= 17 | |
| cg *= 17 | |
| cb *= 17 | |
| default: | |
| return 0, fmt.Errorf("invalid hex color %q: wrong length", color) | |
| } | |
| xc, err := xproto.AllocColor(x.Display, x.Screen.DefaultColormap, uint16(cr)*257, uint16(cg)*257, uint16(cb)*257).Reply() | |
| if err != nil { | |
| return 0, err | |
| } | |
| pixel = xc.Pixel | |
| } else { | |
| xl, err := xproto.LookupColor(x.Display, x.Screen.DefaultColormap, uint16(len(color)), color).Reply() | |
| if err != nil { | |
| return 0, err | |
| } | |
| xc, err := xproto.AllocColor(x.Display, x.Screen.DefaultColormap, xl.ExactRed, xl.ExactGreen, xl.ExactBlue).Reply() | |
| if err != nil { | |
| return 0, err | |
| } | |
| pixel = xc.Pixel | |
| } | |
| xg, err := xproto.NewGcontextId(x.Display) | |
| if err != nil { | |
| return 0, err | |
| } | |
| if err := xproto.CreateGCChecked(x.Display, xg, xproto.Drawable(x.Window), 0, nil).Check(); err != nil { | |
| return 0, err | |
| } | |
| if err := xproto.ChangeGCChecked(x.Display, xg, xproto.GcForeground, []uint32{pixel}).Check(); err != nil { | |
| return 0, err | |
| } | |
| return xg, nil | |
| } | |
| func drawEmpty(x xContext, g geometryContext, color gcColorset) error { | |
| if err := xproto.PolyFillRectangleChecked(x.Display, xproto.Drawable(x.Window), color.BG, []xproto.Rectangle{{ | |
| X: 0, | |
| Y: 0, | |
| Width: uint16(2*(g.Outline+g.Border+g.Padding) + g.SizeX()), | |
| Height: uint16(2*(g.Outline+g.Border+g.Padding) + g.SizeY()), | |
| }}).Check(); err != nil { | |
| return err | |
| } | |
| if err := xproto.PolyFillRectangleChecked(x.Display, xproto.Drawable(x.Window), color.Border, []xproto.Rectangle{{ | |
| X: int16(g.Outline), | |
| Y: int16(g.Outline), | |
| Width: uint16(2*(g.Border+g.Padding) + g.SizeX()), | |
| Height: uint16(2*(g.Border+g.Padding) + g.SizeY()), | |
| }}).Check(); err != nil { | |
| return err | |
| } | |
| if err := xproto.PolyFillRectangleChecked(x.Display, xproto.Drawable(x.Window), color.BG, []xproto.Rectangle{{ | |
| X: int16(g.Outline + g.Border), | |
| Y: int16(g.Outline + g.Border), | |
| Width: uint16(2*+g.Padding + g.SizeX()), | |
| Height: uint16(2*+g.Padding + g.SizeY()), | |
| }}).Check(); err != nil { | |
| return err | |
| } | |
| return nil | |
| } | |
| func drawContent(x xContext, g geometryContext, filledLength int, color xproto.Gcontext) error { | |
| if g.Orientation == OrientationHorizontal { | |
| return xproto.PolyFillRectangleChecked(x.Display, xproto.Drawable(x.Window), color, []xproto.Rectangle{{ | |
| X: int16(g.Outline + g.Border + g.Padding), | |
| Y: int16(g.Outline + g.Border + g.Padding), | |
| Width: uint16(filledLength), | |
| Height: uint16(g.Thickness), | |
| }}).Check() | |
| } else { | |
| return xproto.PolyFillRectangleChecked(x.Display, xproto.Drawable(x.Window), color, []xproto.Rectangle{{ | |
| X: int16(g.Outline + g.Border + g.Padding), | |
| Y: int16(g.Outline + g.Border + g.Padding + g.Length - filledLength), | |
| Width: uint16(g.Thickness), | |
| Height: uint16(filledLength), | |
| }}).Check() | |
| } | |
| } | |
| func drawSeparator(x xContext, g geometryContext, position int, color xproto.Gcontext) error { | |
| if g.Orientation == OrientationHorizontal { | |
| return xproto.PolyFillRectangleChecked(x.Display, xproto.Drawable(x.Window), color, []xproto.Rectangle{{ | |
| X: int16(g.Outline + g.Border + (g.Padding / 2) + position), | |
| Y: int16(g.Outline + g.Border + g.Padding), | |
| Width: uint16(g.Padding), | |
| Height: uint16(g.Thickness), | |
| }}).Check() | |
| } else { | |
| return xproto.PolyFillRectangleChecked(x.Display, xproto.Drawable(x.Window), color, []xproto.Rectangle{{ | |
| X: int16(g.Outline + g.Border + g.Padding), | |
| Y: int16(g.Outline + g.Border + (g.Padding / 2) + g.Length - position), | |
| Width: uint16(g.Thickness), | |
| Height: uint16(g.Padding), | |
| }}).Check() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package xob | |
| type Anchor int | |
| const ( | |
| AnchorTopLeft Anchor = iota | |
| AnchorTopCenter | |
| AnchorTopRight | |
| AnchorMiddleLeft | |
| AnchorMiddleCenter | |
| AnchorMiddleRight | |
| AnchorBottomLeft | |
| AnchorBottomCenter | |
| AnchorBottomRight | |
| ) | |
| type style struct { | |
| X dim | |
| Y dim | |
| Length dim | |
| Thickness int | |
| Border int | |
| Padding int | |
| Outline int | |
| Orientation Orientation | |
| Overflow OverflowMode | |
| Color styleColor | |
| } | |
| type styleColor struct { | |
| Normal colorspec | |
| Overflow colorspec | |
| Alt colorspec | |
| AltOverflow colorspec | |
| } | |
| func (s style) WithX(rel float64, abs int) style { | |
| s.X = dim{rel, abs} | |
| return s | |
| } | |
| func (s style) WithY(rel float64, abs int) style { | |
| s.Y = dim{rel, abs} | |
| return s | |
| } | |
| func (s style) WithLength(rel float64, abs int) style { | |
| s.Length = dim{rel, abs} | |
| return s | |
| } | |
| func (s style) WithThickness(thickness int) style { | |
| s.Thickness = thickness | |
| return s | |
| } | |
| func (s style) WithBorder(border int) style { | |
| s.Border = border | |
| return s | |
| } | |
| func (s style) WithPadding(padding int) style { | |
| s.Padding = padding | |
| return s | |
| } | |
| func (s style) WithOutline(outline int) style { | |
| s.Outline = outline | |
| return s | |
| } | |
| func (s style) WithOrientation(orientation Orientation) style { | |
| s.Orientation = orientation | |
| return s | |
| } | |
| func (s style) WithColorNormal(fg, bg, border string) style { | |
| s.Color.Normal = colorspec{fg, bg, border} | |
| return s | |
| } | |
| func (s style) WithColorOverflow(fg, bg, border string) style { | |
| s.Color.Overflow = colorspec{fg, bg, border} | |
| return s | |
| } | |
| func (s style) WithColorAlt(fg, bg, border string) style { | |
| s.Color.Alt = colorspec{fg, bg, border} | |
| return s | |
| } | |
| func (s style) WithColorAltOverflow(fg, bg, border string) style { | |
| s.Color.AltOverflow = colorspec{fg, bg, border} | |
| return s | |
| } | |
| func (s style) Anchor(anchor Anchor, offset int) style { | |
| switch anchor { | |
| case AnchorTopLeft: | |
| s.X = dim{0.0, offset} | |
| s.Y = dim{0.0, offset} | |
| case AnchorTopCenter: | |
| s.X = dim{0.5, 0} | |
| s.Y = dim{0.0, offset} | |
| case AnchorTopRight: | |
| s.X = dim{1.0, -offset} | |
| s.Y = dim{0.0, offset} | |
| case AnchorMiddleLeft: | |
| s.X = dim{0.0, offset} | |
| s.Y = dim{0.5, 0} | |
| case AnchorMiddleCenter: | |
| s.X = dim{0.5, 0} | |
| s.Y = dim{0.5, 0} | |
| case AnchorMiddleRight: | |
| s.X = dim{1.0, -offset} | |
| s.Y = dim{0.5, 0} | |
| case AnchorBottomLeft: | |
| s.X = dim{0.0, offset} | |
| s.Y = dim{1.0, -offset} | |
| case AnchorBottomCenter: | |
| s.X = dim{0.5, 0} | |
| s.Y = dim{1.0, -offset} | |
| case AnchorBottomRight: | |
| s.X = dim{1.0, -offset} | |
| s.Y = dim{1.0, -offset} | |
| } | |
| return s | |
| } | |
| func DefaultStyle() style { | |
| return style{ | |
| X: dim{ | |
| Rel: 1.0, | |
| Abs: -48, | |
| }, | |
| Y: dim{ | |
| Rel: 0.5, | |
| Abs: 0, | |
| }, | |
| Length: dim{ | |
| Rel: 0.3, | |
| Abs: 0, | |
| }, | |
| Thickness: 24, | |
| Border: 4, | |
| Padding: 3, | |
| Outline: 3, | |
| Orientation: OrientationVertical, | |
| Color: styleColor{ | |
| Normal: colorspec{ | |
| FG: "#ffffff", | |
| BG: "#000000", | |
| Border: "#ffffff", | |
| }, | |
| Overflow: colorspec{ | |
| FG: "#ff0000", | |
| BG: "#000000", | |
| Border: "#ff0000", | |
| }, | |
| Alt: colorspec{ | |
| FG: "#555555", | |
| BG: "#000000", | |
| Border: "#555555", | |
| }, | |
| AltOverflow: colorspec{ | |
| FG: "#550000", | |
| BG: "#000000", | |
| Border: "#550000", | |
| }, | |
| }, | |
| } | |
| } | |
| func I3Style() style { | |
| return style{ | |
| X: dim{ | |
| Rel: 1.0, | |
| Abs: -48, | |
| }, | |
| Y: dim{ | |
| Rel: 0.5, | |
| Abs: 0, | |
| }, | |
| Length: dim{ | |
| Rel: 0.3, | |
| Abs: 0, | |
| }, | |
| Thickness: 24, | |
| Border: 1, | |
| Padding: 0, | |
| Outline: 0, | |
| Orientation: OrientationVertical, | |
| Color: styleColor{ | |
| Normal: colorspec{ | |
| FG: "#285577", | |
| BG: "#222222", | |
| Border: "#4c7899", | |
| }, | |
| Alt: colorspec{ | |
| FG: "#333333", | |
| BG: "#222222", | |
| Border: "#444444", | |
| }, | |
| Overflow: colorspec{ | |
| FG: "#900000", | |
| BG: "#222222", | |
| Border: "#933333", | |
| }, | |
| AltOverflow: colorspec{ | |
| FG: "#900000", | |
| BG: "#222222", | |
| Border: "#444444", | |
| }, | |
| }, | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment