From aa33b320d33cb3a7725fa53429266bcdcbbed7d3 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Thu, 15 Oct 2020 10:28:41 +0100 Subject: [PATCH 01/10] Hook up keyoard shortcuts now it's in Fyne --- internal/x11/wm/desk.go | 23 +++++++++++++++++------ internal/x11/wm/screenshot.go | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 internal/x11/wm/screenshot.go diff --git a/internal/x11/wm/desk.go b/internal/x11/wm/desk.go index 8930649e..5db4c732 100644 --- a/internal/x11/wm/desk.go +++ b/internal/x11/wm/desk.go @@ -68,11 +68,12 @@ const ( moveResizeMoveKeyboard moveResizeType = 10 moveResizeCancel moveResizeType = 11 - keyCodeEscape = 9 - keyCodeTab = 23 - keyCodeReturn = 36 - keyCodeAlt = 64 - keyCodeSpace = 65 + keyCodeEscape = 9 + keyCodeTab = 23 + keyCodeReturn = 36 + keyCodeAlt = 64 + keyCodeSpace = 65 + keyCodePrintScreen = 107 keyCodeEnter = 108 keyCodeLeft = 113 @@ -223,6 +224,8 @@ func (x *x11WM) keyNameToCode(n fyne.KeyName) xproto.Keycode { switch n { case fyne.KeySpace: return keyCodeSpace + case deskDriver.KeyPrintScreen: + return keyCodePrintScreen case fyne.KeyTab: return keyCodeTab case fynedesk.KeyBrightnessDown: @@ -253,7 +256,6 @@ func (x *x11WM) modifierToKeyMask(m deskDriver.Modifier) uint16 { } func (x *x11WM) runLoop() { - x.setupBindings() conn := x.x.Conn() for { @@ -516,6 +518,7 @@ func (x *x11WM) setInitialWindowAttributes(win xproto.Window) { } func (x *x11WM) setupBindings() { + x.setupDeskBindings() deskListener := make(chan fynedesk.DeskSettings) fynedesk.Instance().Settings().AddChangeListener(deskListener) go func() { @@ -541,6 +544,14 @@ func (x *x11WM) setupBindings() { }() } +func (x *x11WM) setupDeskBindings() { + fynedesk.Instance().AddShortcut(&fynedesk.Shortcut{Name: "Print Window", KeyName: deskDriver.KeyPrintScreen, + Modifier: deskDriver.ShiftModifier}, + x.screenshotWindow) + fynedesk.Instance().AddShortcut(&fynedesk.Shortcut{Name: "Print Screen", KeyName: deskDriver.KeyPrintScreen}, + x.screenshot) +} + func (x *x11WM) setupWindow(win xproto.Window) { if x11.WindowName(x.x, win) == "" { x11.WindowExtendedHintsAdd(x.x, win, "_NET_WM_STATE_SKIP_TASKBAR") diff --git a/internal/x11/wm/screenshot.go b/internal/x11/wm/screenshot.go new file mode 100644 index 00000000..8a6e7749 --- /dev/null +++ b/internal/x11/wm/screenshot.go @@ -0,0 +1,22 @@ +package wm + +import ( + "log" + + "fyne.io/fyne" +) + +func (x *x11WM) screenshot() { + log.Println("Trying to print screen") + +} + +func (x *x11WM) screenshotWindow() { + win := x.stack.TopWindow() + if win == nil { + fyne.LogError("Unable to print window with no window visible", nil) + return + } + + log.Println("Trying to print window:", win.Properties().Title()) +} From 738a4adeba71720e0ee76f58df092f59af2a7e30 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 17 Oct 2020 16:09:10 +0100 Subject: [PATCH 02/10] First pass window screenshotting. Works for single windows well. Root windows need work --- internal/x11/wm/screenshot.go | 91 ++++++++++++++++++++++++++++-- internal/x11/wm/screenshot_test.go | 18 ++++++ 2 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 internal/x11/wm/screenshot_test.go diff --git a/internal/x11/wm/screenshot.go b/internal/x11/wm/screenshot.go index 8a6e7749..fc9221e1 100644 --- a/internal/x11/wm/screenshot.go +++ b/internal/x11/wm/screenshot.go @@ -1,14 +1,49 @@ package wm import ( - "log" + "image" + "image/png" + "math" "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/container" + "fyne.io/fyne/dialog" + "fyne.io/fyne/layout" + "fyne.io/fyne/storage" + "fyne.io/fyne/widget" + "github.com/BurntSushi/xgb/xproto" + + "fyne.io/fynedesk/internal/x11" ) -func (x *x11WM) screenshot() { - log.Println("Trying to print screen") +func (x *x11WM) captureWindow(win xproto.Window) { + draw := xproto.Drawable(win) + geom, err := xproto.GetGeometry(x.x.Conn(), draw).Reply() + if err != nil { + fyne.LogError("Unable to get screen geometry", err) + return + } + pix, err := xproto.GetImage(x.x.Conn(), xproto.ImageFormatZPixmap, draw, 0, 0, geom.Width, geom.Height, + math.MaxUint32).Reply() + if err != nil { + fyne.LogError("Error capturing window content", err) + return + } + img := image.NewNRGBA(image.Rect(0, 0, int(geom.Width), int(geom.Height))) + i := 0 + for y := 0; y < int(geom.Height); y++ { + for x := 0; x < int(geom.Width); x++ { + swapPixels(pix.Data, img.Pix, i) + i += 4 + } + } + x.showCaptureSave(img) +} + +func (x *x11WM) screenshot() { + x.captureWindow(x.rootIDs[0]) // TODO combine all windows on all screens } func (x *x11WM) screenshotWindow() { @@ -18,5 +53,53 @@ func (x *x11WM) screenshotWindow() { return } - log.Println("Trying to print window:", win.Properties().Title()) + x.captureWindow(win.(x11.XWin).FrameID()) +} + +func (x *x11WM) showCaptureSave(img image.Image) { + w := fyne.CurrentApp().NewWindow("Screenshot") + save := widget.NewButton("Save...", func() { + saveImage(img, w) + }) + save.Importance = widget.HighImportance + buttons := container.NewHBox( + layout.NewSpacer(), + widget.NewButton("Cancel", func() { + w.Close() + }), + save) + + preview := canvas.NewImageFromImage(img) + preview.FillMode = canvas.ImageFillContain + w.SetContent(container.NewBorder(nil, buttons, nil, nil, preview)) + w.Resize(fyne.NewSize(400, 250)) + w.Show() +} + +func saveImage(pix image.Image, w fyne.Window) { + d := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) { + if err != nil { + dialog.ShowError(err, w) + } + + err = png.Encode(write, pix) + if err != nil { + dialog.ShowError(err, w) + } + + w.Close() + }, w) + d.SetFilter(storage.NewMimeTypeFileFilter([]string{"image/png"})) + d.Show() +} + +func swapPixels(in []byte, out []uint8, i int) { + b := in[i] + g := in[i+1] + r := in[i+2] + // a is ignored, seems to be 0 for border + out[i] = r + out[i+1] = g + out[i+2] = b + out[i+3] = 0xff } diff --git a/internal/x11/wm/screenshot_test.go b/internal/x11/wm/screenshot_test.go new file mode 100644 index 00000000..5e4cad29 --- /dev/null +++ b/internal/x11/wm/screenshot_test.go @@ -0,0 +1,18 @@ +package wm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSwapPixel(t *testing.T) { + in := []byte{0xff, 0x99, 0x33, 0x00} + out := []uint8{0xff, 0xff, 0xff, 0xff} + swapPixels(in, out, 0) + + assert.Equal(t, uint8(0x33), out[0]) + assert.Equal(t, uint8(0x99), out[1]) + assert.Equal(t, uint8(0xff), out[2]) + assert.Equal(t, uint8(0xff), out[3]) +} From e46695c9c26297628adc533adef0b2a2016da373 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 17 Oct 2020 16:15:25 +0100 Subject: [PATCH 03/10] missed build flags --- internal/x11/wm/screenshot.go | 2 ++ internal/x11/wm/screenshot_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/internal/x11/wm/screenshot.go b/internal/x11/wm/screenshot.go index fc9221e1..db520c45 100644 --- a/internal/x11/wm/screenshot.go +++ b/internal/x11/wm/screenshot.go @@ -1,3 +1,5 @@ +// +build linux openbsd freebsd netbsd + package wm import ( diff --git a/internal/x11/wm/screenshot_test.go b/internal/x11/wm/screenshot_test.go index 5e4cad29..d1730e6f 100644 --- a/internal/x11/wm/screenshot_test.go +++ b/internal/x11/wm/screenshot_test.go @@ -1,3 +1,5 @@ +// +build linux openbsd freebsd netbsd + package wm import ( From 641e4d6eb6ffbb82a0cf4c5ec5d897d76bece363 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 17 Oct 2020 22:53:04 +0100 Subject: [PATCH 04/10] Fix refactored gocyclo --- .github/workflows/code_checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_checks.yml b/.github/workflows/code_checks.yml index 0009aedf..c829c092 100644 --- a/.github/workflows/code_checks.yml +++ b/.github/workflows/code_checks.yml @@ -13,7 +13,7 @@ jobs: - name: get dependencies run: | GO111MODULE=off go get golang.org/x/tools/cmd/goimports - GO111MODULE=off go get github.com/fzipp/gocyclo + GO111MODULE=off go get github.com/fzipp/gocyclo/cmd/gocyclo GO111MODULE=off go get golang.org/x/lint/golint # GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck - name: cleanup repository From 78e89c56d783cdc4d53cafadc4161bb3d8ecd385 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sun, 18 Oct 2020 10:59:23 +0100 Subject: [PATCH 05/10] Capture root window for complete screenshot --- internal/x11/wm/screenshot.go | 49 ++++++++++++++++-------------- internal/x11/wm/screenshot_test.go | 4 +-- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/internal/x11/wm/screenshot.go b/internal/x11/wm/screenshot.go index db520c45..9f21423f 100644 --- a/internal/x11/wm/screenshot.go +++ b/internal/x11/wm/screenshot.go @@ -19,33 +19,34 @@ import ( "fyne.io/fynedesk/internal/x11" ) -func (x *x11WM) captureWindow(win xproto.Window) { - draw := xproto.Drawable(win) - geom, err := xproto.GetGeometry(x.x.Conn(), draw).Reply() +func (x *x11WM) captureWindow(win xproto.Window) *image.NRGBA { + geom, err := xproto.GetGeometry(x.x.Conn(), xproto.Drawable(win)).Reply() if err != nil { fyne.LogError("Unable to get screen geometry", err) - return + return nil } - pix, err := xproto.GetImage(x.x.Conn(), xproto.ImageFormatZPixmap, draw, 0, 0, geom.Width, geom.Height, - math.MaxUint32).Reply() + pix, err := xproto.GetImage(x.x.Conn(), xproto.ImageFormatZPixmap, xproto.Drawable(win), + 0, 0, geom.Width, geom.Height, math.MaxUint32).Reply() if err != nil { fyne.LogError("Error capturing window content", err) - return + return nil } img := image.NewNRGBA(image.Rect(0, 0, int(geom.Width), int(geom.Height))) i := 0 for y := 0; y < int(geom.Height); y++ { for x := 0; x < int(geom.Width); x++ { - swapPixels(pix.Data, img.Pix, i) + copyPixel(pix.Data, img.Pix, i) i += 4 } } - x.showCaptureSave(img) + return img } func (x *x11WM) screenshot() { - x.captureWindow(x.rootIDs[0]) // TODO combine all windows on all screens + root := x.x.RootWin() + bg := x.captureWindow(root) + x.showCaptureSave(bg) } func (x *x11WM) screenshotWindow() { @@ -55,7 +56,11 @@ func (x *x11WM) screenshotWindow() { return } - x.captureWindow(win.(x11.XWin).FrameID()) + img := x.captureWindow(win.(x11.XWin).FrameID()) + if img == nil { + return + } + x.showCaptureSave(img) } func (x *x11WM) showCaptureSave(img image.Image) { @@ -78,6 +83,17 @@ func (x *x11WM) showCaptureSave(img image.Image) { w.Show() } +func copyPixel(in []byte, out []uint8, i int) { + b := in[i] + g := in[i+1] + r := in[i+2] + // we ignore a - seems to be 0 for border + out[i] = r + out[i+1] = g + out[i+2] = b + out[i+3] = 0xff +} + func saveImage(pix image.Image, w fyne.Window) { d := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) { if err != nil { @@ -94,14 +110,3 @@ func saveImage(pix image.Image, w fyne.Window) { d.SetFilter(storage.NewMimeTypeFileFilter([]string{"image/png"})) d.Show() } - -func swapPixels(in []byte, out []uint8, i int) { - b := in[i] - g := in[i+1] - r := in[i+2] - // a is ignored, seems to be 0 for border - out[i] = r - out[i+1] = g - out[i+2] = b - out[i+3] = 0xff -} diff --git a/internal/x11/wm/screenshot_test.go b/internal/x11/wm/screenshot_test.go index d1730e6f..f0fe4ef1 100644 --- a/internal/x11/wm/screenshot_test.go +++ b/internal/x11/wm/screenshot_test.go @@ -8,10 +8,10 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSwapPixel(t *testing.T) { +func TestCopyPixel(t *testing.T) { in := []byte{0xff, 0x99, 0x33, 0x00} out := []uint8{0xff, 0xff, 0xff, 0xff} - swapPixels(in, out, 0) + copyPixel(in, out, 0) assert.Equal(t, uint8(0x33), out[0]) assert.Equal(t, uint8(0x99), out[1]) From a3890e7e38cfc7d6a70c884bf6cecdbaaa80ee55 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 19 Oct 2020 08:39:57 +0100 Subject: [PATCH 06/10] Fix our alpha handling in borders --- internal/x11/win/frame.go | 2 +- internal/x11/wm/screenshot.go | 4 ++-- internal/x11/wm/screenshot_test.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/x11/win/frame.go b/internal/x11/win/frame.go index 7f63d1b2..67d1c901 100644 --- a/internal/x11/win/frame.go +++ b/internal/x11/win/frame.go @@ -231,7 +231,7 @@ func (f *frame) copyDecorationPixels(width, height, xoff, yoff uint32, img image data[i] = byte(b) data[i+1] = byte(g) data[i+2] = byte(r) - data[i+3] = 0 + data[i+3] = 0xff i += 4 } diff --git a/internal/x11/wm/screenshot.go b/internal/x11/wm/screenshot.go index 9f21423f..ece160ad 100644 --- a/internal/x11/wm/screenshot.go +++ b/internal/x11/wm/screenshot.go @@ -87,11 +87,11 @@ func copyPixel(in []byte, out []uint8, i int) { b := in[i] g := in[i+1] r := in[i+2] - // we ignore a - seems to be 0 for border + a := in[i+3] out[i] = r out[i+1] = g out[i+2] = b - out[i+3] = 0xff + out[i+3] = a } func saveImage(pix image.Image, w fyne.Window) { diff --git a/internal/x11/wm/screenshot_test.go b/internal/x11/wm/screenshot_test.go index f0fe4ef1..0a61981e 100644 --- a/internal/x11/wm/screenshot_test.go +++ b/internal/x11/wm/screenshot_test.go @@ -9,8 +9,8 @@ import ( ) func TestCopyPixel(t *testing.T) { - in := []byte{0xff, 0x99, 0x33, 0x00} - out := []uint8{0xff, 0xff, 0xff, 0xff} + in := []byte{0xff, 0x99, 0x33, 0xff} + out := []uint8{0, 0, 0, 0} copyPixel(in, out, 0) assert.Equal(t, uint8(0x33), out[0]) From 9aeab3406853486d7114f5e3a9058b1ffb66e1ac Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 19 Oct 2020 09:06:12 +0100 Subject: [PATCH 07/10] Refactor the screenshot UI code out of X11 This will now work with any WM implementation we add :) --- internal/ui/desk.go | 5 + internal/ui/embed_wm.go | 6 ++ internal/ui/screenshot.go | 70 ++++++++++++++ internal/x11/screenshot.go | 49 ++++++++++ internal/x11/{wm => }/screenshot_test.go | 2 +- internal/x11/win/client.go | 6 ++ internal/x11/wm/desk.go | 15 ++- internal/x11/wm/screenshot.go | 112 ----------------------- test/desktop.go | 7 ++ test/window.go | 11 ++- window.go | 27 +++--- wm.go | 3 + 12 files changed, 179 insertions(+), 134 deletions(-) create mode 100644 internal/ui/screenshot.go create mode 100644 internal/x11/screenshot.go rename internal/x11/{wm => }/screenshot_test.go (96%) delete mode 100644 internal/x11/wm/screenshot.go diff --git a/internal/ui/desk.go b/internal/ui/desk.go index cdce52b0..bd5e4f2b 100644 --- a/internal/ui/desk.go +++ b/internal/ui/desk.go @@ -304,6 +304,11 @@ func (l *desktop) registerShortcuts() { func() { // dummy - the wm handles app switcher }) + fynedesk.Instance().AddShortcut(&fynedesk.Shortcut{Name: "Print Window", KeyName: deskDriver.KeyPrintScreen, + Modifier: deskDriver.ShiftModifier}, + l.screenshotWindow) + fynedesk.Instance().AddShortcut(&fynedesk.Shortcut{Name: "Print Screen", KeyName: deskDriver.KeyPrintScreen}, + l.screenshot) } // Screens returns the screens provider of the current desktop environment for access to screen functionality. diff --git a/internal/ui/embed_wm.go b/internal/ui/embed_wm.go index 1161e226..c30d929b 100644 --- a/internal/ui/embed_wm.go +++ b/internal/ui/embed_wm.go @@ -1,6 +1,8 @@ package ui import ( + "image" + "fyne.io/fyne" "fyne.io/fynedesk" @@ -52,6 +54,10 @@ func (e *embededWM) Blank() { // no-op, we don't control screen brightness } +func (e *embededWM) Capture() image.Image { + return nil // would mean accessing the underling OS screen functions... +} + func (e *embededWM) Close() { windows := fyne.CurrentApp().Driver().AllWindows() if len(windows) > 0 { diff --git a/internal/ui/screenshot.go b/internal/ui/screenshot.go new file mode 100644 index 00000000..6f736d8f --- /dev/null +++ b/internal/ui/screenshot.go @@ -0,0 +1,70 @@ +package ui + +import ( + "image" + "image/png" + + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/container" + "fyne.io/fyne/dialog" + "fyne.io/fyne/layout" + "fyne.io/fyne/storage" + "fyne.io/fyne/widget" +) + +func (l *desktop) screenshot() { + bg := l.wm.Capture() + l.showCaptureSave(bg) +} + +func (l *desktop) screenshotWindow() { + win := l.wm.TopWindow() + if win == nil { + fyne.LogError("Unable to print window with no window visible", nil) + return + } + + img := win.Capture() + if img == nil { + return + } + l.showCaptureSave(img) +} + +func (l *desktop) showCaptureSave(img image.Image) { + w := fyne.CurrentApp().NewWindow("Screenshot") + save := widget.NewButton("Save...", func() { + saveImage(img, w) + }) + save.Importance = widget.HighImportance + buttons := container.NewHBox( + layout.NewSpacer(), + widget.NewButton("Cancel", func() { + w.Close() + }), + save) + + preview := canvas.NewImageFromImage(img) + preview.FillMode = canvas.ImageFillContain + w.SetContent(container.NewBorder(nil, buttons, nil, nil, preview)) + w.Resize(fyne.NewSize(400, 250)) + w.Show() +} + +func saveImage(pix image.Image, w fyne.Window) { + d := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) { + if err != nil { + dialog.ShowError(err, w) + } + + err = png.Encode(write, pix) + if err != nil { + dialog.ShowError(err, w) + } + + w.Close() + }, w) + d.SetFilter(storage.NewMimeTypeFileFilter([]string{"image/png"})) + d.Show() +} diff --git a/internal/x11/screenshot.go b/internal/x11/screenshot.go new file mode 100644 index 00000000..7c179f8f --- /dev/null +++ b/internal/x11/screenshot.go @@ -0,0 +1,49 @@ +// +build linux openbsd freebsd netbsd + +package x11 + +import ( + "image" + "math" + + "fyne.io/fyne" + "github.com/BurntSushi/xgb" + "github.com/BurntSushi/xgb/xproto" +) + +// CaptureWindow allows x11 code to get the image representation of a screen area. +// The window specified will be captured according to its bounds. +func CaptureWindow(conn *xgb.Conn, win xproto.Window) *image.NRGBA { + geom, err := xproto.GetGeometry(conn, xproto.Drawable(win)).Reply() + if err != nil { + fyne.LogError("Unable to get screen geometry", err) + return nil + } + pix, err := xproto.GetImage(conn, xproto.ImageFormatZPixmap, xproto.Drawable(win), + 0, 0, geom.Width, geom.Height, math.MaxUint32).Reply() + if err != nil { + fyne.LogError("Error capturing window content", err) + return nil + } + + img := image.NewNRGBA(image.Rect(0, 0, int(geom.Width), int(geom.Height))) + i := 0 + for y := 0; y < int(geom.Height); y++ { + for x := 0; x < int(geom.Width); x++ { + copyPixel(pix.Data, img.Pix, i) + i += 4 + } + } + return img +} + +func copyPixel(in []byte, out []uint8, i int) { + b := in[i] + g := in[i+1] + r := in[i+2] + a := in[i+3] + out[i] = r + out[i+1] = g + out[i+2] = b + out[i+3] = a +} diff --git a/internal/x11/wm/screenshot_test.go b/internal/x11/screenshot_test.go similarity index 96% rename from internal/x11/wm/screenshot_test.go rename to internal/x11/screenshot_test.go index 0a61981e..3e998525 100644 --- a/internal/x11/wm/screenshot_test.go +++ b/internal/x11/screenshot_test.go @@ -1,6 +1,6 @@ // +build linux openbsd freebsd netbsd -package wm +package x11 import ( "testing" diff --git a/internal/x11/win/client.go b/internal/x11/win/client.go index 4ef6d033..a7dbaf02 100644 --- a/internal/x11/win/client.go +++ b/internal/x11/win/client.go @@ -3,6 +3,8 @@ package win import ( + "image" + "fyne.io/fyne" "github.com/BurntSushi/xgb/xproto" @@ -63,6 +65,10 @@ func NewClient(win xproto.Window, wm x11.XWM) x11.XWin { return c } +func (c *client) Capture() image.Image { + return x11.CaptureWindow(c.wm.Conn(), c.FrameID()) +} + func (c *client) ChildID() xproto.Window { return c.win } diff --git a/internal/x11/wm/desk.go b/internal/x11/wm/desk.go index 5db4c732..e7c775f7 100644 --- a/internal/x11/wm/desk.go +++ b/internal/x11/wm/desk.go @@ -5,6 +5,7 @@ package wm // import "fyne.io/fynedesk/internal/x11/wm" import ( "errors" "fmt" + "image" "os" "os/exec" "strconv" @@ -148,6 +149,11 @@ func (x *x11WM) Blank() { }() } +func (x *x11WM) Capture() image.Image { + root := x.x.RootWin() + return x11.CaptureWindow(x.x.Conn(), root) +} + func (x *x11WM) Close() { for _, child := range x.clients { child.Close() @@ -518,7 +524,6 @@ func (x *x11WM) setInitialWindowAttributes(win xproto.Window) { } func (x *x11WM) setupBindings() { - x.setupDeskBindings() deskListener := make(chan fynedesk.DeskSettings) fynedesk.Instance().Settings().AddChangeListener(deskListener) go func() { @@ -544,14 +549,6 @@ func (x *x11WM) setupBindings() { }() } -func (x *x11WM) setupDeskBindings() { - fynedesk.Instance().AddShortcut(&fynedesk.Shortcut{Name: "Print Window", KeyName: deskDriver.KeyPrintScreen, - Modifier: deskDriver.ShiftModifier}, - x.screenshotWindow) - fynedesk.Instance().AddShortcut(&fynedesk.Shortcut{Name: "Print Screen", KeyName: deskDriver.KeyPrintScreen}, - x.screenshot) -} - func (x *x11WM) setupWindow(win xproto.Window) { if x11.WindowName(x.x, win) == "" { x11.WindowExtendedHintsAdd(x.x, win, "_NET_WM_STATE_SKIP_TASKBAR") diff --git a/internal/x11/wm/screenshot.go b/internal/x11/wm/screenshot.go deleted file mode 100644 index ece160ad..00000000 --- a/internal/x11/wm/screenshot.go +++ /dev/null @@ -1,112 +0,0 @@ -// +build linux openbsd freebsd netbsd - -package wm - -import ( - "image" - "image/png" - "math" - - "fyne.io/fyne" - "fyne.io/fyne/canvas" - "fyne.io/fyne/container" - "fyne.io/fyne/dialog" - "fyne.io/fyne/layout" - "fyne.io/fyne/storage" - "fyne.io/fyne/widget" - "github.com/BurntSushi/xgb/xproto" - - "fyne.io/fynedesk/internal/x11" -) - -func (x *x11WM) captureWindow(win xproto.Window) *image.NRGBA { - geom, err := xproto.GetGeometry(x.x.Conn(), xproto.Drawable(win)).Reply() - if err != nil { - fyne.LogError("Unable to get screen geometry", err) - return nil - } - pix, err := xproto.GetImage(x.x.Conn(), xproto.ImageFormatZPixmap, xproto.Drawable(win), - 0, 0, geom.Width, geom.Height, math.MaxUint32).Reply() - if err != nil { - fyne.LogError("Error capturing window content", err) - return nil - } - - img := image.NewNRGBA(image.Rect(0, 0, int(geom.Width), int(geom.Height))) - i := 0 - for y := 0; y < int(geom.Height); y++ { - for x := 0; x < int(geom.Width); x++ { - copyPixel(pix.Data, img.Pix, i) - i += 4 - } - } - return img -} - -func (x *x11WM) screenshot() { - root := x.x.RootWin() - bg := x.captureWindow(root) - x.showCaptureSave(bg) -} - -func (x *x11WM) screenshotWindow() { - win := x.stack.TopWindow() - if win == nil { - fyne.LogError("Unable to print window with no window visible", nil) - return - } - - img := x.captureWindow(win.(x11.XWin).FrameID()) - if img == nil { - return - } - x.showCaptureSave(img) -} - -func (x *x11WM) showCaptureSave(img image.Image) { - w := fyne.CurrentApp().NewWindow("Screenshot") - save := widget.NewButton("Save...", func() { - saveImage(img, w) - }) - save.Importance = widget.HighImportance - buttons := container.NewHBox( - layout.NewSpacer(), - widget.NewButton("Cancel", func() { - w.Close() - }), - save) - - preview := canvas.NewImageFromImage(img) - preview.FillMode = canvas.ImageFillContain - w.SetContent(container.NewBorder(nil, buttons, nil, nil, preview)) - w.Resize(fyne.NewSize(400, 250)) - w.Show() -} - -func copyPixel(in []byte, out []uint8, i int) { - b := in[i] - g := in[i+1] - r := in[i+2] - a := in[i+3] - out[i] = r - out[i+1] = g - out[i+2] = b - out[i+3] = a -} - -func saveImage(pix image.Image, w fyne.Window) { - d := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) { - if err != nil { - dialog.ShowError(err, w) - } - - err = png.Encode(write, pix) - if err != nil { - dialog.ShowError(err, w) - } - - w.Close() - }, w) - d.SetFilter(storage.NewMimeTypeFileFilter([]string{"image/png"})) - d.Show() -} diff --git a/test/desktop.go b/test/desktop.go index bf3bfbb9..c5ba5bdb 100644 --- a/test/desktop.go +++ b/test/desktop.go @@ -1,6 +1,8 @@ package test import ( + "image" + "fyne.io/fyne" "fyne.io/fyne/test" @@ -34,6 +36,11 @@ func (*Desktop) AddShortcut(shortcut *fynedesk.Shortcut, handler func()) { // TODO } +// Capture the desktop to an image. Our test code cowardly refuses to do this. +func (*Desktop) Capture() image.Image { + return nil // could be implemented if required for testing +} + // ContentSizePixels returns a default value for how much space maximised apps should use func (*Desktop) ContentSizePixels(_ *fynedesk.Screen) (uint32, uint32) { return uint32(320), uint32(240) diff --git a/test/window.go b/test/window.go index 96e74ee8..711b5553 100644 --- a/test/window.go +++ b/test/window.go @@ -1,6 +1,10 @@ package test -import "fyne.io/fynedesk" +import ( + "image" + + "fyne.io/fynedesk" +) // Window is an in-memory virtual window for test purposes type Window struct { @@ -16,6 +20,11 @@ func NewWindow(title string) *Window { return win } +// Capture the contents of the window. Our test code cowardly refuses to do this. +func (w *Window) Capture() image.Image { + return nil // we can add this if required for testing +} + // Close this test window func (w *Window) Close() { // no-op diff --git a/window.go b/window.go index 5a21054b..c6d2f1ae 100644 --- a/window.go +++ b/window.go @@ -1,6 +1,10 @@ package fynedesk -import "fyne.io/fyne" +import ( + "image" + + "fyne.io/fyne" +) // Window represents a single managed window within a window manager. // There may be borders or not depending on configuration. @@ -11,16 +15,17 @@ type Window interface { Maximized() bool // Is the window Maximized? TopWindow() bool // Is this the window on top? - Close() // Close this window and possibly the application running it - Focus() // Ask this window to get input focus - Fullscreen() // Request to fullscreen this window - Iconify() // Request to iconify this window - Maximize() // Request to resize this window to it's largest possible size - RaiseAbove(Window) // Raise this window above a given other window - RaiseToTop() // Raise this window to the top of the stack - Unfullscreen() // Request to unfullscreen this window - Uniconify() // Request to restore this window and possibly children of this window from being minimized - Unmaximize() // Request to restore this window to its size before being maximized + Capture() image.Image // Capture the contents of this window to an image + Close() // Close this window and possibly the application running it + Focus() // Ask this window to get input focus + Fullscreen() // Request to fullscreen this window + Iconify() // Request to iconify this window + Maximize() // Request to resize this window to it's largest possible size + RaiseAbove(Window) // Raise this window above a given other window + RaiseToTop() // Raise this window to the top of the stack + Unfullscreen() // Request to unfullscreen this window + Uniconify() // Request to restore this window and possibly children of this window from being minimized + Unmaximize() // Request to restore this window to its size before being maximized Properties() WindowProperties // Request the properties set on this window } diff --git a/wm.go b/wm.go index 5e43869f..4ef1bc56 100644 --- a/wm.go +++ b/wm.go @@ -1,11 +1,14 @@ package fynedesk +import "image" + // WindowManager describes a full window manager which may be loaded as part of the setup. type WindowManager interface { Stack AddStackListener(StackListener) Blank() + Capture() image.Image // Capture the contents of the whole desktop to an image Close() Run() } From 1b615735bd072c9eaf00bb83254656e32ac58d9a Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 8 Dec 2020 18:02:04 +0000 Subject: [PATCH 08/10] Remove an optimisation that broke on clean startup --- internal/x11/wm/desk.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/internal/x11/wm/desk.go b/internal/x11/wm/desk.go index 9be49811..428a76a5 100644 --- a/internal/x11/wm/desk.go +++ b/internal/x11/wm/desk.go @@ -58,8 +58,6 @@ type x11WM struct { rootID xproto.Window transientMap map[xproto.Window][]xproto.Window oldRoot *xgraphics.Image - - primaryX, primaryY, primaryW, primaryH int } type moveResizeType uint32 @@ -349,12 +347,6 @@ func (x *x11WM) configureRoots() { maxY = fyne.Max(maxY, screen.Y+screen.Height) if screen == fynedesk.Instance().Screens().Primary() { - if x.primaryX == screen.X && x.primaryY == screen.Y && - x.primaryW == screen.Width && x.primaryH == screen.Height { - continue // our screen has not changed - } - x.primaryX, x.primaryY = screen.X, screen.Y - x.primaryW, x.primaryH = screen.Width, screen.Height notifyEv := xproto.ConfigureNotifyEvent{Event: x.rootID, Window: x.rootID, AboveSibling: 0, X: int16(screen.X), Y: int16(screen.Y), Width: uint16(screen.Width), Height: uint16(screen.Height), BorderWidth: 0, OverrideRedirect: false} From 953a42f8293b02d8745c63d4ac49e4bca8ab1c89 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 14 Dec 2020 23:10:08 +0000 Subject: [PATCH 09/10] Attempt the strange missing window fix --- internal/x11/screenshot.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/x11/screenshot.go b/internal/x11/screenshot.go index 7c179f8f..53c30cc6 100644 --- a/internal/x11/screenshot.go +++ b/internal/x11/screenshot.go @@ -41,9 +41,8 @@ func copyPixel(in []byte, out []uint8, i int) { b := in[i] g := in[i+1] r := in[i+2] - a := in[i+3] out[i] = r out[i+1] = g out[i+2] = b - out[i+3] = a + out[i+3] = 0xff // some colour maps / depths don't include alpha } From bce62c2585cf2f0dfcfa5b0956128e7743c311cd Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 15 Dec 2020 18:34:45 +0000 Subject: [PATCH 10/10] Fix notes on PR --- internal/ui/screenshot.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/ui/screenshot.go b/internal/ui/screenshot.go index 6f736d8f..c2e807d9 100644 --- a/internal/ui/screenshot.go +++ b/internal/ui/screenshot.go @@ -48,12 +48,15 @@ func (l *desktop) showCaptureSave(img image.Image) { preview := canvas.NewImageFromImage(img) preview.FillMode = canvas.ImageFillContain w.SetContent(container.NewBorder(nil, buttons, nil, nil, preview)) - w.Resize(fyne.NewSize(400, 250)) + w.Resize(fyne.NewSize(480, 360)) w.Show() } func saveImage(pix image.Image, w fyne.Window) { d := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) { + if write == nil { // cancelled + return + } if err != nil { dialog.ShowError(err, w) }