Compare commits

...

14 commits

Author SHA1 Message Date
bc50f37187
better version 2024-06-20 15:57:51 -05:00
95e640c9e8
eww closes the bar on it's own 2024-06-20 15:49:13 -05:00
68c90481fc
add --json flag to hyprman mako 2024-06-11 11:53:13 -05:00
b31326b529
update flake 2024-06-06 13:31:35 -05:00
36dd259822
simplify mako impl 2024-06-06 13:25:01 -05:00
269cc8e53f
go mod 2024-06-06 13:11:30 -05:00
64813f5a9c
formatting 2024-06-06 13:11:21 -05:00
a32a141867
implement basic makoctl notification list 2024-06-01 01:28:23 -05:00
372fd3b36f
use 2 seconds not 200... 2024-05-31 16:31:14 -05:00
95e7001077
split off eww file 2024-05-31 16:30:18 -05:00
f698f2614e
fix completions 2024-05-31 15:10:28 -05:00
05b641bc97
gofmt 2024-05-31 14:59:23 -05:00
42ac8bee8c
flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/e381a1288138aceda0ac63db32c7be545b446921' (2024-05-21)
  → 'github:nixos/nixpkgs/6132b0f6e344ce2fe34fc051b72fb46e34f668e0' (2024-05-30)
2024-05-31 14:50:53 -05:00
4fcd597b6d
update nix vendor hash 2024-05-31 14:50:48 -05:00
12 changed files with 300 additions and 211 deletions

View file

@ -5,12 +5,13 @@ import (
)
var ewwCmd = &cobra.Command{
Use:"eww" ,
Short: "eww integration",
Use: "eww",
Short: "eww integration",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
hm.LoadConfig(configPath)
},
}
func init() {
rootCmd.AddCommand(ewwCmd)
rootCmd.AddCommand(ewwCmd)
}

View file

@ -8,7 +8,7 @@ var startCmd = &cobra.Command{
Use: "start",
Short: "Launch eww",
Run: func(cmd *cobra.Command, args []string) {
hm.LaunchEww()
hm.LaunchEww()
},
}

View file

@ -8,7 +8,7 @@ var watchCmd = &cobra.Command{
Use: "watch",
Short: "Watch hyprland events and propagate to eww",
Run: func(cmd *cobra.Command, args []string) {
hm.Watch()
hm.Watch()
},
}

View file

@ -1,22 +1,26 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
// "git.dayl.in/daylin/hyprman/internal"
hyprman "git.dayl.in/daylin/hyprman/internal"
)
var makoCmd = &cobra.Command{
Use:"mako" ,
Short: "mako integration",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hahah mako baby")
},
Use: "mako",
Short: "mako integration",
Run: func(cmd *cobra.Command, args []string) {
hyprman.ListNotifications(number, json)
},
}
var (
number int
json bool
)
func init() {
rootCmd.AddCommand(makoCmd)
rootCmd.AddCommand(makoCmd)
makoCmd.Flags().IntVarP(&number, "number", "n", 10, "number of notifications")
makoCmd.Flags().BoolVar(&json, "json", false, "output data as json")
}

View file

@ -2,11 +2,12 @@ package cmd
import (
"os"
cc "github.com/ivanpirog/coloredcobra"
"git.dayl.in/daylin/hyprman/internal"
"github.com/spf13/cobra"
)
cc "github.com/ivanpirog/coloredcobra"
"github.com/spf13/cobra"
hyprman "git.dayl.in/daylin/hyprman/internal"
)
func Execute() {
cc.Init(&cc.Config{
@ -25,22 +26,17 @@ func Execute() {
}
}
var configPath string
var hm = &hyprman.Hyprman{}
var (
configPath string
hm = &hyprman.Hyprman{}
)
var rootCmd = &cobra.Command{
Use: "hyprman",
Short: "hyprland companion app",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
hm.LoadConfig(configPath)
},
}
func init() {
rootCmd.CompletionOptions.HiddenDefaultCmd = true
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", hyprman.DefaultConfigPath(), "path to config file")
rootCmd.CompletionOptions.HiddenDefaultCmd = true
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", hyprman.DefaultConfigPath(), "path to config file")
}

View file

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1716312448,
"narHash": "sha256-PH3w5av8d+TdwCkiWN4UPBTxrD9MpxIQPDVWctlomVo=",
"lastModified": 1717646450,
"narHash": "sha256-KE+UmfSVk5PG8jdKdclPVcMrUB8yVZHbsjo7ZT1Bm3c=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e381a1288138aceda0ac63db32c7be545b446921",
"rev": "818dbe2f96df233d2041739d6079bb616d3e5597",
"type": "github"
},
"original": {

View file

@ -22,7 +22,7 @@
in
{
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
default = pkgs.mkShell {
packages = with pkgs; [go];
};
});
@ -30,9 +30,9 @@
default = self.packages.${pkgs.system}.hyprman;
hyprman = pkgs.buildGoModule {
pname = "hyprman";
version = "unstable";
version = "${self.shortRev or "dirty"}";
src = cleanSource ./.;
vendorHash = "sha256-eKeUhS2puz6ALb+cQKl7+DGvm9Cl+miZAHX0imf9wdg=";
vendorHash = "sha256-hJwRLVIiWxLbX2tAPVVVLGFk4OaAy5qiFcICEqhVMJM=";
nativeBuildInputs = with pkgs; [installShellFiles];

7
go.mod
View file

@ -3,16 +3,23 @@ module git.dayl.in/daylin/hyprman
go 1.22.2
require (
github.com/charmbracelet/lipgloss v0.11.0
github.com/goccy/go-yaml v1.11.3
github.com/ivanpirog/coloredcobra v1.0.1
github.com/spf13/cobra v1.8.0
)
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/x/ansi v0.1.2 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect

15
go.sum
View file

@ -1,3 +1,9 @@
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/daylinmorgan/coloredcobra v0.0.0-20240527152736-9d3ce38297a6 h1:Zst7HlWvQj8LPJ3mhIWtGH2DxqUo6VEq0oekNyAho6M=
@ -20,11 +26,20 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
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.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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=

122
internal/eww.go Normal file
View file

@ -0,0 +1,122 @@
package hyprman
import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"net"
"os/exec"
"strings"
"time"
)
type EwwWorkspace struct {
Icon string `json:"icon"`
Class string `json:"class"`
Id int `json:"id"`
}
func (hm *Hyprman) pickIcon(class string) string {
for k, v := range hm.Config.Classes {
if strings.Contains(k, class) {
return v
}
}
return hm.Config.DefaultIcon
}
func (hm *Hyprman) generateEwwClasses() {
monitors := getMonitors()
clients := getClients()
var ewwClasses [][]EwwWorkspace
monitor := make([]EwwWorkspace, 9)
for i := range 9 {
monitor[i] = EwwWorkspace{
hm.Config.NoClientIcon,
fmt.Sprintf("ws-button-%d", i+1),
i + 1,
}
}
for _, client := range clients {
id := client.Workspace.Id - 1
currentIcon := monitor[id].Icon
if currentIcon == hm.Config.NoClientIcon {
monitor[id].Icon = hm.pickIcon(client.Class)
} else {
monitor[id].Icon = fmt.Sprintf("%s%s", currentIcon, hm.pickIcon(client.Class))
}
}
for _, id := range openWorkspaces(monitors) {
// activeId := m.ActiveWorkspace.Id
monitor[id-1].Class = fmt.Sprintf("%s %s", monitor[id-1].Class, "ws-button-open")
}
for i, m := range monitors {
activeId := m.ActiveWorkspace.Id - 1
ewwClasses = append(ewwClasses, make([]EwwWorkspace, 9))
copy(ewwClasses[i], monitor)
ewwClasses[i][activeId].Class = fmt.Sprintf("%s %s-%d", monitor[activeId].Class, "ws-button-active", activeId+1)
}
bytes, err := json.Marshal(ewwClasses)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
}
func ewwBar1(cmd string) {
time.Sleep(3 * time.Second)
output, err := exec.Command("eww", cmd, "bar1").CombinedOutput()
if err != nil {
notify(fmt.Sprintf("failed to %s bar 1\n\n%s\n\n%v", cmd, output, err))
}
}
func (hm *Hyprman) handleHyprEvent(line string) {
s := strings.Split(line, ">>")
event, _ := s[0], s[1]
if event == "monitoradded" {
notify("Monitor added opening bar1")
go ewwBar1("open")
}
hm.generateEwwClasses()
}
func (hm *Hyprman) LaunchEww() {
for i := range getMonitors() {
if err := exec.Command("eww", "open", fmt.Sprintf("bar%d", i)).Run(); err != nil {
notify(fmt.Sprintf("Error lanching eww:\n%v", err))
}
}
}
func (hm *Hyprman) Watch() {
socketPath := hyprSocket2()
conn, err := net.Dial("unix", socketPath)
if err != nil {
log.Fatalln("Error connecting to hyprland IPC:", err)
}
defer conn.Close()
reader := bufio.NewReader(conn)
hm.generateEwwClasses()
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
// log.Printf("reached EOF for %s\n", socketPath)
break
}
// log.Println("error reading line")
continue
}
hm.handleHyprEvent(line)
}
}

View file

@ -1,17 +1,13 @@
package hyprman
import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
yaml "github.com/goccy/go-yaml"
)
@ -133,21 +129,6 @@ func getClients() []Client {
return clients
}
type EwwWorkspace struct {
Icon string `json:"icon"`
Class string `json:"class"`
Id int `json:"id"`
}
func (hm *Hyprman) pickIcon(class string) string {
for k, v := range hm.Config.Classes {
if strings.Contains(k, class) {
return v
}
}
return hm.Config.DefaultIcon
}
func openWorkspaces(monitors []Monitor) []int {
open := make([]int, len(monitors))
for i, m := range monitors {
@ -156,125 +137,12 @@ func openWorkspaces(monitors []Monitor) []int {
return open
}
func (hm *Hyprman) generateEwwClasses() {
monitors := getMonitors()
clients := getClients()
var ewwClasses [][]EwwWorkspace
monitor := make([]EwwWorkspace, 9)
for i := range 9 {
monitor[i] = EwwWorkspace{
hm.Config.NoClientIcon,
fmt.Sprintf("ws-button-%d", i+1),
i + 1,
}
}
for _, client := range clients {
id := client.Workspace.Id - 1
currentIcon := monitor[id].Icon
if currentIcon == hm.Config.NoClientIcon {
monitor[id].Icon = hm.pickIcon(client.Class)
} else {
monitor[id].Icon = fmt.Sprintf("%s%s", currentIcon, hm.pickIcon(client.Class))
}
}
for _, id := range openWorkspaces(monitors) {
// activeId := m.ActiveWorkspace.Id
monitor[id-1].Class = fmt.Sprintf("%s %s", monitor[id-1].Class, "ws-button-open")
}
for i, m := range monitors {
activeId := m.ActiveWorkspace.Id - 1
ewwClasses = append(ewwClasses, make([]EwwWorkspace, 9))
copy(ewwClasses[i], monitor)
ewwClasses[i][activeId].Class = fmt.Sprintf("%s %s-%d", monitor[activeId].Class, "ws-button-active", activeId+1)
}
bytes, err := json.Marshal(ewwClasses)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
}
func ewwBar1(cmd string) {
time.Sleep(200 * time.Second)
if err := exec.Command("eww", cmd, "bar1").Run(); err != nil {
log.Fatal(err)
}
}
func notify(message string) {
cmd := exec.Command("notify-send", "hyprman", message)
cmd := exec.Command(
"notify-send",
"--app-name=hyprman",
"--transient",
message,
)
cmd.Run()
}
func (hm *Hyprman) handleHyprEvent(line string) {
s := strings.Split(line, ">>")
event, _ := s[0], s[1]
switch event {
case "monitorremoved":
notify("Monitor removed closing bar1")
go ewwBar1("close")
hm.generateEwwClasses()
case "monitoradded":
notify("Monitor added opening bar1")
go ewwBar1("open")
hm.generateEwwClasses()
case "workspace",
"focusedmon",
"activewindow",
"createworkspace",
"destroyworkspace",
"moveworkspace",
"renameworkspace",
"openwindow",
"closewindow",
"movewindow",
"movewindowv2",
"changefloatingmode",
"windowtitle",
"togglegroup",
"moveintogroup",
"moveoutofgroup",
"configreloaded":
hm.generateEwwClasses()
}
}
func (hm *Hyprman) LaunchEww() {
for i := range getMonitors() {
if err := exec.Command("eww", "open", fmt.Sprintf("bar%d", i)).Run(); err != nil {
notify(fmt.Sprintf("Error lanching eww:\n%v", err))
}
}
}
func (hm *Hyprman) Watch() {
socketPath := hyprSocket2()
conn, err := net.Dial("unix", socketPath)
if err != nil {
log.Fatalln("Error connecting to hyprland IPC:", err)
}
defer conn.Close()
reader := bufio.NewReader(conn)
hm.generateEwwClasses()
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
// log.Printf("reached EOF for %s\n", socketPath)
break
}
// log.Println("error reading line")
continue
}
hm.handleHyprEvent(line)
}
}

View file

@ -1,36 +1,112 @@
package hyprman
// import (
// "log"
// "os/exec"
// )
//
// type MakoHistory struct {
// Type string `json:"type"`
// Data [][]MakoNotification `json:"data"`
// }
//
// type MakoNotification struct {
// AppName MakoNotificationData `json:"app-name"`
// // AppIcon MakoNotificationData `json:"app-icon"`
// // Category MakoNotificationData
// // DesktopEntry MakoNotificationData `json:"desktop-entry"`
// Summary MakoNotificationData `json:"summary"`
// Body MakoNotificationData
// // Id MakoNotificationData
// // Urgency MakoNotificationData
// // Actions MakoNotificationData
// }
//
// type MakoNotificationData struct {
// Type string `json:"type"`
// Data string `json:"data"`
// }
//
// func getHistory() MakoHistory {
// makoOutput, err := exec.Command("makoctl", "history").CombinedOutput()
// if err != nil {
// log.Fatal(err)
// }
// }
import (
"encoding/json"
"fmt"
"log"
"os/exec"
"github.com/charmbracelet/lipgloss"
)
type MakoHistory struct {
Type string `json:"type"`
Data [][]MakoNotification `json:"data"`
}
type MakoNotification struct {
AppName MakoNotificationData `json:"app-name"`
Summary MakoNotificationData
Body MakoNotificationData
// AppIcon MakoNotificationData `json:"app-icon"`
// Category MakoNotificationData
// DesktopEntry MakoNotificationData `json:"desktop-entry"`
// Id MakoNotificationData
// Urgency MakoNotificationData
// Actions MakoNotificationData
}
type MakoNotificationData struct {
Type string `json:"type"`
Data string `json:"data"`
}
type History struct {
Notifications []Notifcation
}
type Notifcation struct {
AppName string
Summary string
Body string
}
func (mn MakoNotification) toNotification() Notifcation {
var n Notifcation
n.AppName = mn.AppName.Data
n.Summary = mn.Summary.Data
n.Body = mn.Body.Data
return n
}
func (mh MakoHistory) toHistory() History {
var h History
var notifications []Notifcation
for _, mn := range mh.Data[0] {
notifications = append(notifications, mn.toNotification())
}
h.Notifications = notifications
return h
}
func getHistory() History {
var makoHistory MakoHistory
makoOutput, err := exec.Command("makoctl", "history").CombinedOutput()
if err != nil {
log.Fatal(err)
}
json.Unmarshal(makoOutput, &makoHistory)
return makoHistory.toHistory()
}
func (h *History) truncate(number int) {
h.Notifications = h.Notifications[:min(len(h.Notifications), number)]
}
func ListNotifications(number int, outputJson bool) {
history := getHistory()
history.truncate(number)
if !outputJson {
for _, n := range history.Notifications {
n.Render()
}
} else {
b, err := json.MarshalIndent(history.Notifications, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
}
}
func (n Notifcation) Render() {
appStyle := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("3")).
PaddingTop(1)
textStyle := lipgloss.NewStyle().Width(80).PaddingLeft(2).
BorderStyle(lipgloss.ThickBorder()).
BorderLeft(true)
fmt.Println(appStyle.Render(n.AppName))
if len(n.Summary) > 0 {
fmt.Println(textStyle.
BorderForeground(lipgloss.Color("14")).
Render(n.Summary))
}
if len(n.Body) > 0 {
fmt.Println(textStyle.
BorderForeground(lipgloss.Color("2")).
Render(n.Body))
}
}