summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-10-20 07:37:43 +0300
committerPetri Hienonen <petri.hienonen@gmail.com>2025-10-20 07:37:43 +0300
commit384c9986b867caca5a58329b364bf6c82ea29173 (patch)
treed36ee565b433b9d6dcae08e0a77d444961bf22ca
downloadcopper-384c9986b867caca5a58329b364bf6c82ea29173.tar.zst
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--flake.lock61
-rw-r--r--flake.nix68
-rw-r--r--go.mod17
-rw-r--r--go.sum23
-rw-r--r--main.go113
6 files changed, 283 insertions, 0 deletions
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 <item><title> 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)
+ }
+ }
+}