From 384c9986b867caca5a58329b364bf6c82ea29173 Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Mon, 20 Oct 2025 07:37:43 +0300 Subject: Initial commit --- .gitignore | 1 + flake.lock | 61 +++++++++++++++++++++++++++++++++ flake.nix | 68 +++++++++++++++++++++++++++++++++++++ go.mod | 17 ++++++++++ go.sum | 23 +++++++++++++ main.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 283 insertions(+) create mode 100644 .gitignore create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16d3c4d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.cache diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..0d6154b --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1760878510, + "narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2be9687 --- /dev/null +++ b/flake.nix @@ -0,0 +1,68 @@ +{ + description = "Minimal Gio (gioui.org) development environment with Go support"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + }; + + gioDeps = with pkgs; [ + go + gopls + git + pkg-config + # X11 + Wayland deps + xorg.libX11 + xorg.libXcursor + xorg.libXrandr + xorg.libXinerama + xorg.libXi + xorg.libXext + libGL + wayland + wayland-protocols + libxkbcommon + # Text rendering + cairo + pango + harfbuzz + freetype + fontconfig + ]; + in + { + devShells.default = pkgs.mkShell { + name = "gio-dev"; + packages = gioDeps; + + shellHook = '' + export CGO_ENABLED=1 + export PKG_CONFIG_PATH=${pkgs.lib.makeSearchPath "lib/pkgconfig" gioDeps} + export GIO_USE_WAYLAND=1 + echo "✅ Gio development shell ready." + echo "Run 'go run .' to start your app." + ''; + }; + + apps.default = { + type = "app"; + program = "${pkgs.go}/bin/go run ."; + }; + } + ); +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3b20ab9 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module copper + +go 1.24.6 + +require ( + gioui.org v0.9.0 + golang.org/x/net v0.39.0 +) + +require ( + gioui.org/shader v1.0.8 // indirect + github.com/go-text/typesetting v0.3.0 // indirect + golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/image v0.26.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.24.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cb8d10f --- /dev/null +++ b/go.sum @@ -0,0 +1,23 @@ +eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY= +eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= +gioui.org v0.9.0 h1:4u7XZwnb5kzQW91Nz/vR0wKD6LdW9CaVF96r3rfy4kc= +gioui.org v0.9.0/go.mod h1:CjNig0wAhLt9WZxOPAusgFD8x8IRvqt26LdDBa3Jvao= +gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= +gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= +gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= +github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4= +github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80= +golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8= +golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= +golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= diff --git a/main.go b/main.go new file mode 100644 index 0000000..7b55ae3 --- /dev/null +++ b/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" + + "golang.org/x/net/html" + + "gioui.org/app" + "gioui.org/font/gofont" + "gioui.org/io/system" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/unit" + "gioui.org/widget/material" +) + +// FeedItem represents a single RSS entry +type FeedItem struct { + Title string + Link string +} + +// Simple RSS fetcher (parses only and <link>) +func fetchRSS(url string) ([]FeedItem, error) { + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + doc, err := html.Parse(resp.Body) + if err != nil { + return nil, err + } + + var items []FeedItem + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "item" { + var item FeedItem + for c := n.FirstChild; c != nil; c = c.NextSibling { + if c.Type == html.ElementNode { + switch c.Data { + case "title": + if c.FirstChild != nil { + item.Title = c.FirstChild.Data + } + case "link": + if c.FirstChild != nil { + item.Link = c.FirstChild.Data + } + } + } + } + items = append(items, item) + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + return items, nil +} + +func main() { + go func() { + w := app.NewWindow( + app.Title("MiniRSS"), + app.Size(unit.Dp(400), unit.Dp(600)), + ) + if err := loop(w); err != nil { + log.Fatal(err) + } + os.Exit(0) + }() + app.Main() +} + +func loop(w *app.Window) error { + th := material.NewTheme(gofont.Collection()) + + // Replace with any RSS feed you want to test + feedURL := "https://xkcd.com/atom.xml" + items, err := fetchRSS(feedURL) + if err != nil { + return fmt.Errorf("failed to fetch RSS: %w", err) + } + + list := layout.List{ + Axis: layout.Vertical, + } + + var ops op.Ops + for { + e := <-w.Events() + switch e := e.(type) { + case system.DestroyEvent: + return e.Err + case system.FrameEvent: + gtx := layout.NewContext(&ops, e) + + list.Layout(gtx, len(items), func(gtx layout.Context, i int) layout.Dimensions { + title := material.H6(th, items[i].Title) + return title.Layout(gtx) + }) + + e.Frame(gtx.Ops) + } + } +} -- cgit v1.2.3-70-g09d2