Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package cosmic-comp for openSUSE:Factory checked in at 2026-08-28 19:57:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/cosmic-comp (Old) and /work/SRC/openSUSE:Factory/.cosmic-comp.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "cosmic-comp" Fri Aug 28 19:57:53 2026 rev:15 rq:1374427 version:1.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/cosmic-comp/cosmic-comp.changes 2026-08-21 16:55:41.670994550 +0200 +++ /work/SRC/openSUSE:Factory/.cosmic-comp.new.1265/cosmic-comp.changes 2026-08-28 20:00:58.134473447 +0200 @@ -1,0 +2,12 @@ +Thu Aug 27 04:25:44 UTC 2026 - Richard Rahl <[email protected]> + +- Update to version 1.7.0: + * improv: clear image_cache on refresh + * improv: round up and down sample cursor sizes to limit cache + * feat: scalable cursor support + * Clamp zoom level to 1.0 if < 1.01 + * a11y: magnifier: change zoom stepping to multiplicative, bound max, add presets + * feat: shake cursor to find by magnifying the cursor + * fix: treat unlocking the session as user activity + +------------------------------------------------------------------- Old: ---- cosmic-comp-1.6.0.obscpio New: ---- cosmic-comp-1.7.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ cosmic-comp.spec ++++++ --- /var/tmp/diff_new_pack.3QF0Lf/_old 2026-08-28 20:01:00.467555015 +0200 +++ /var/tmp/diff_new_pack.3QF0Lf/_new 2026-08-28 20:01:00.474555260 +0200 @@ -17,7 +17,7 @@ Name: cosmic-comp -Version: 1.6.0 +Version: 1.7.0 Release: 0 Summary: Compositor for the COSMIC DE License: GPL-3.0-only ++++++ _service ++++++ --- /var/tmp/diff_new_pack.3QF0Lf/_old 2026-08-28 20:01:00.561558302 +0200 +++ /var/tmp/diff_new_pack.3QF0Lf/_new 2026-08-28 20:01:00.578558896 +0200 @@ -3,7 +3,7 @@ <service name="obs_scm" mode="manual"> <param name="url">https://github.com/pop-os/cosmic-comp.git</param> <param name="scm">git</param> - <param name="revision">refs/tags/epoch-1.6.0</param> + <param name="revision">refs/tags/epoch-1.7.0</param> <param name="versionrewrite-pattern">epoch-(.*)</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-replacement">\1</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.3QF0Lf/_old 2026-08-28 20:01:00.624560505 +0200 +++ /var/tmp/diff_new_pack.3QF0Lf/_new 2026-08-28 20:01:00.633560819 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/pop-os/cosmic-comp.git</param> - <param name="changesrevision">314fc670a83f1e570b069cd709df809ba99fa334</param></service></servicedata> + <param name="changesrevision">31827ed2409f92d3bc224d3ce601c6f2136183fb</param></service></servicedata> (No newline at EOF) ++++++ cosmic-comp-1.6.0.obscpio -> cosmic-comp-1.7.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/FETCH_HEAD new/cosmic-comp-1.7.0/.git/FETCH_HEAD --- old/cosmic-comp-1.6.0/.git/FETCH_HEAD 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/FETCH_HEAD 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1,53 @@ +df7819b2b24bb6b66ca97a2456572410f3e05521 not-for-merge branch 'action-on-typing' of https://github.com/pop-os/cosmic-comp +669d497b8ccdc9bce2663c2e87ac2114ceca3c57 not-for-merge branch 'checklist_nobuild' of https://github.com/pop-os/cosmic-comp +84b99f934688a80fffc6fa53d46c1aaad2d39ebc not-for-merge branch 'cleanup-corner-redraw' of https://github.com/pop-os/cosmic-comp +d47754d41e915bb5e5a698970beb93a9a04eaa88 not-for-merge branch 'comp-output-redirect_noble' of https://github.com/pop-os/cosmic-comp +8eaa3a8b6831bec6eb7ffdfe7e91711edc18c656 not-for-merge branch 'cursor_noble' of https://github.com/pop-os/cosmic-comp +020c3361e6b02ac320f67cfded3a29b9c5b285a3 not-for-merge branch 'delay-capture_noble' of https://github.com/pop-os/cosmic-comp +38bcf97553aaf35d708708905e7566ec309486ce not-for-merge branch 'dispatch2_noble' of https://github.com/pop-os/cosmic-comp +3c8c9edfded44b72738a6c26c4551fab8dbf2ff7 not-for-merge branch 'dpms_noble' of https://github.com/pop-os/cosmic-comp +bbb5e4a9c65c621ae307e781b4f9aaa24ef6b54d not-for-merge branch 'drm-order-test' of https://github.com/pop-os/cosmic-comp +2892e9408c4e043b92ea938cc782706bfc3b1aa9 not-for-merge branch 'edid-match' of https://github.com/pop-os/cosmic-comp +aa698b337cdffd7b0c8bde9f3a468a1aae90f783 not-for-merge branch 'ext-screencopy_nobuild' of https://github.com/pop-os/cosmic-comp +eb7f079ae4ec36963f3cc079f12fe58b45303b5c not-for-merge branch 'ext-workspace-capture_noble' of https://github.com/pop-os/cosmic-comp +5894ff2eea4fdbde43e05d8e27a2b05588254ceb not-for-merge branch 'fifo_noble' of https://github.com/pop-os/cosmic-comp +4d7aa5b8c6779ed3d6401ddc8d5a247ba15554ef not-for-merge branch 'fix-2275_noble' of https://github.com/pop-os/cosmic-comp +ac443217178bb46075b1a98f5921f12a8f0b5580 not-for-merge branch 'fix-3330_noble' of https://github.com/pop-os/cosmic-comp +12f18758154535e1f8d4ad98d5bb755dc01830e8 not-for-merge branch 'fix-check-all-connectors_noble' of https://github.com/pop-os/cosmic-comp +8d3cb6d5159b5ba8f423cbebcc9c9f2cbadf274d not-for-merge branch 'fix-late-cloexec-session_noble' of https://github.com/pop-os/cosmic-comp +822b68cab19963cf12a252d6b43984e45d7e3586 not-for-merge branch 'fix-name-owners' of https://github.com/pop-os/cosmic-comp +324a8c6eb52f4099d02f06c2c65e11b33438d994 not-for-merge branch 'fix-positioning' of https://github.com/pop-os/cosmic-comp +ba20ddd2076d02cb8141d23a684c6da075864b47 not-for-merge branch 'fix-positioning-2' of https://github.com/pop-os/cosmic-comp +9c72388ac8d5fee2008ac9f290bf4d4c3fcf9f9c not-for-merge branch 'fix-use-10-bpc_noble' of https://github.com/pop-os/cosmic-comp +beac0214f4f5aad54cb1432fd08d6f2890ace582 not-for-merge branch 'fix-wipe-dpms-on-resume' of https://github.com/pop-os/cosmic-comp +d3016f142c182edf7dd529fe9be7314f6cabd87a not-for-merge branch 'fix_dnd_events' of https://github.com/pop-os/cosmic-comp +b942a9a64754aaca9c8ab0b5dd2fc59e5ef8823b not-for-merge branch 'focus-follow-border_noble' of https://github.com/pop-os/cosmic-comp +e7843332b34b430ae2f1e6c0570bfd82bea767d5 not-for-merge branch 'iced-rebase' of https://github.com/pop-os/cosmic-comp +dde9279c51dcdd4e3aca09d67af12dc5b0f9718f not-for-merge branch 'image-copy-session-stop_noble' of https://github.com/pop-os/cosmic-comp +47470fe63f3b0481f58b85a23a41453502e11eb0 not-for-merge branch 'image-copy_noble' of https://github.com/pop-os/cosmic-comp +1ae9d1e68fcef34b202b87913812dbfdeec33dd6 not-for-merge branch 'inhibitor-lock_nobuild' of https://github.com/pop-os/cosmic-comp +5c93094574b31800caaa27255caac4a7253f24ad not-for-merge branch 'master' of https://github.com/pop-os/cosmic-comp +1d00a51ca9ec3ccee83b1aa2a776f054a729d1d0 not-for-merge branch 'master_jammy' of https://github.com/pop-os/cosmic-comp +ebc268e6ae954ea96aaf8c9c671822c6aa8a6a2a not-for-merge branch 'migration-enter_noble' of https://github.com/pop-os/cosmic-comp +f72781f3acb695b4ae574181a4569dc60fa79176 not-for-merge branch 'mst-path_noble' of https://github.com/pop-os/cosmic-comp +05b99e8e3439e441ff97dc460dd1bc6aeb4afb1b not-for-merge branch 'pointer-capture-wip_nobuild' of https://github.com/pop-os/cosmic-comp +745a65e33b4c1335b97096e744bb5d345124fa64 not-for-merge branch 'position-hint_noble' of https://github.com/pop-os/cosmic-comp +198ac1eeb4d6dfcf450e41da5f31bf2598f4d258 not-for-merge branch 'reis' of https://github.com/pop-os/cosmic-comp +d9ba816a43c365fbece7e55c1d278794a0f5fe08 not-for-merge branch 'remaining-clippy_noble' of https://github.com/pop-os/cosmic-comp +0cdb2b7169cc602d706505b2e6367d62db29fed9 not-for-merge branch 'screencopy-damage_noble' of https://github.com/pop-os/cosmic-comp +b265c9217a381be67b61f7308cb9b71cd4aebc07 not-for-merge branch 'screencopy-no-invalid_noble' of https://github.com/pop-os/cosmic-comp +1670b4c682998bcbd9bb17e5855d1f2c718a2dc2 not-for-merge branch 'session-lock-frosted-subsurface' of https://github.com/pop-os/cosmic-comp +c135f76cc5494fff676987435ec548fba8c0c775 not-for-merge branch 'ssd-preference_noble' of https://github.com/pop-os/cosmic-comp +620c1a730d270d206d017cd5aee16ebff946fbda not-for-merge branch 'stack' of https://github.com/pop-os/cosmic-comp +7ef4c56f6629fe3feadb3f0bbf8df73d1a6223ff not-for-merge branch 'stack-previous-keyboard_noble' of https://github.com/pop-os/cosmic-comp +55cef24f4d5348a20cd4fe0b173fee84c0fc8923 not-for-merge branch 'strict-activation_jammy' of https://github.com/pop-os/cosmic-comp +93baf72971b37039f929c608f5d85dff2de2e681 not-for-merge branch 'suspend-key' of https://github.com/pop-os/cosmic-comp +374b690b8d7c22c20f250c6e95da23b50d5f937b not-for-merge branch 'tablet-update_noble' of https://github.com/pop-os/cosmic-comp +5505b25c81e38331902cca620d78ac2cac34badb not-for-merge branch 'testing-cosmic-comp-pr2656_noble' of https://github.com/pop-os/cosmic-comp +d760be5201a0e46242e6319d2e4e142b7cceba45 not-for-merge branch 'tgl-flicker' of https://github.com/pop-os/cosmic-comp +a39f380291fb01462853b198efb6cd14e3f1d6ef not-for-merge branch 'toplevel-info_noble' of https://github.com/pop-os/cosmic-comp +0d7da43cbc75766611d8f692affd52fb0e830201 not-for-merge branch 'touch_noble' of https://github.com/pop-os/cosmic-comp +b66849a309e49ebc04ee3ab78a7cca3e47a43661 not-for-merge branch 'udmabuf-sharing' of https://github.com/pop-os/cosmic-comp +08b82773c077a31e62cac127e7e6c3319ce78df5 not-for-merge branch 'update-smithay_noble' of https://github.com/pop-os/cosmic-comp +a80578d191469a2ba8bed58589c2dabaa67a65ae not-for-merge branch 'wayland-max-buffer-size_noble' of https://github.com/pop-os/cosmic-comp +3c8a52067a41142ff9e83d630dd584e1e02887e3 not-for-merge branch 'xdg-system-bell_noble' of https://github.com/pop-os/cosmic-comp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/HEAD new/cosmic-comp-1.7.0/.git/HEAD --- old/cosmic-comp-1.6.0/.git/HEAD 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/.git/HEAD 2026-08-25 17:33:56.000000000 +0200 @@ -1 +1 @@ -314fc670a83f1e570b069cd709df809ba99fa334 +31827ed2409f92d3bc224d3ce601c6f2136183fb diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/ORIG_HEAD new/cosmic-comp-1.7.0/.git/ORIG_HEAD --- old/cosmic-comp-1.6.0/.git/ORIG_HEAD 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/.git/ORIG_HEAD 2026-08-25 17:33:56.000000000 +0200 @@ -1 +1 @@ -314fc670a83f1e570b069cd709df809ba99fa334 +31827ed2409f92d3bc224d3ce601c6f2136183fb Binary files old/cosmic-comp-1.6.0/.git/index and new/cosmic-comp-1.7.0/.git/index differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/logs/HEAD new/cosmic-comp-1.7.0/.git/logs/HEAD --- old/cosmic-comp-1.6.0/.git/logs/HEAD 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/.git/logs/HEAD 2026-08-25 17:33:56.000000000 +0200 @@ -1,2 +1,3 @@ 0000000000000000000000000000000000000000 314fc670a83f1e570b069cd709df809ba99fa334 Richard Rahl <[email protected]> 1787139434 +0200 clone: from https://github.com/pop-os/cosmic-comp.git 314fc670a83f1e570b069cd709df809ba99fa334 314fc670a83f1e570b069cd709df809ba99fa334 Richard Rahl <[email protected]> 1787139436 +0200 checkout: moving from master to refs/tags/epoch-1.6.0 +314fc670a83f1e570b069cd709df809ba99fa334 31827ed2409f92d3bc224d3ce601c6f2136183fb Richard Rahl <user@t14.(none)> 1787804744 +0200 merge refs/tags/epoch-1.7.0: Fast-forward diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/fix-check-all-connectors_noble new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/fix-check-all-connectors_noble --- old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/fix-check-all-connectors_noble 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/fix-check-all-connectors_noble 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +9c95ebfcf4c4cb678e1dd359cf1e685c759d5739 12f18758154535e1f8d4ad98d5bb755dc01830e8 Richard Rahl <user@t14.(none)> 1787804742 +0200 fetch --tags: forced-update diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/focus-follow-border_noble new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/focus-follow-border_noble --- old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/focus-follow-border_noble 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/focus-follow-border_noble 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +d915d08f80aa07bb71494a693edbdb24049aa8a3 b942a9a64754aaca9c8ab0b5dd2fc59e5ef8823b Richard Rahl <user@t14.(none)> 1787804742 +0200 fetch --tags: forced-update diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/master new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/master --- old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/master 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/master 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +314fc670a83f1e570b069cd709df809ba99fa334 5c93094574b31800caaa27255caac4a7253f24ad Richard Rahl <user@t14.(none)> 1787804742 +0200 fetch --tags: fast-forward diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/testing-cosmic-comp-pr2656_noble new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/testing-cosmic-comp-pr2656_noble --- old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/testing-cosmic-comp-pr2656_noble 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/testing-cosmic-comp-pr2656_noble 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 5505b25c81e38331902cca620d78ac2cac34badb Richard Rahl <user@t14.(none)> 1787804742 +0200 fetch --tags: storing head diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/update-smithay_noble new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/update-smithay_noble --- old/cosmic-comp-1.6.0/.git/logs/refs/remotes/origin/update-smithay_noble 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/logs/refs/remotes/origin/update-smithay_noble 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +d96d9e52a8c39b7c4195c47f4766dd6d24851a91 08b82773c077a31e62cac127e7e6c3319ce78df5 Richard Rahl <user@t14.(none)> 1787804742 +0200 fetch --tags: forced-update diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/objects/info/commit-graphs/commit-graph-chain new/cosmic-comp-1.7.0/.git/objects/info/commit-graphs/commit-graph-chain --- old/cosmic-comp-1.6.0/.git/objects/info/commit-graphs/commit-graph-chain 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/objects/info/commit-graphs/commit-graph-chain 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +d457831da5339573fa6aacea0b57247e6bc1ea73 Binary files old/cosmic-comp-1.6.0/.git/objects/info/commit-graphs/graph-d457831da5339573fa6aacea0b57247e6bc1ea73.graph and new/cosmic-comp-1.7.0/.git/objects/info/commit-graphs/graph-d457831da5339573fa6aacea0b57247e6bc1ea73.graph differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-2aa37258aca0791aacefc8999491c85af3885fd6.idx and new/cosmic-comp-1.7.0/.git/objects/pack/pack-2aa37258aca0791aacefc8999491c85af3885fd6.idx differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-2aa37258aca0791aacefc8999491c85af3885fd6.pack and new/cosmic-comp-1.7.0/.git/objects/pack/pack-2aa37258aca0791aacefc8999491c85af3885fd6.pack differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-2aa37258aca0791aacefc8999491c85af3885fd6.rev and new/cosmic-comp-1.7.0/.git/objects/pack/pack-2aa37258aca0791aacefc8999491c85af3885fd6.rev differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-53ae127e8f33c4a339f28b1b3ad39f18e67470df.idx and new/cosmic-comp-1.7.0/.git/objects/pack/pack-53ae127e8f33c4a339f28b1b3ad39f18e67470df.idx differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-53ae127e8f33c4a339f28b1b3ad39f18e67470df.pack and new/cosmic-comp-1.7.0/.git/objects/pack/pack-53ae127e8f33c4a339f28b1b3ad39f18e67470df.pack differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-53ae127e8f33c4a339f28b1b3ad39f18e67470df.rev and new/cosmic-comp-1.7.0/.git/objects/pack/pack-53ae127e8f33c4a339f28b1b3ad39f18e67470df.rev differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-c06bebfd96f9e6439e02edceb7e625010161296e.idx and new/cosmic-comp-1.7.0/.git/objects/pack/pack-c06bebfd96f9e6439e02edceb7e625010161296e.idx differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-c06bebfd96f9e6439e02edceb7e625010161296e.pack and new/cosmic-comp-1.7.0/.git/objects/pack/pack-c06bebfd96f9e6439e02edceb7e625010161296e.pack differ Binary files old/cosmic-comp-1.6.0/.git/objects/pack/pack-c06bebfd96f9e6439e02edceb7e625010161296e.rev and new/cosmic-comp-1.7.0/.git/objects/pack/pack-c06bebfd96f9e6439e02edceb7e625010161296e.rev differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/refs/remotes/origin/fix-check-all-connectors_noble new/cosmic-comp-1.7.0/.git/refs/remotes/origin/fix-check-all-connectors_noble --- old/cosmic-comp-1.6.0/.git/refs/remotes/origin/fix-check-all-connectors_noble 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/refs/remotes/origin/fix-check-all-connectors_noble 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +12f18758154535e1f8d4ad98d5bb755dc01830e8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/refs/remotes/origin/focus-follow-border_noble new/cosmic-comp-1.7.0/.git/refs/remotes/origin/focus-follow-border_noble --- old/cosmic-comp-1.6.0/.git/refs/remotes/origin/focus-follow-border_noble 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/refs/remotes/origin/focus-follow-border_noble 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +b942a9a64754aaca9c8ab0b5dd2fc59e5ef8823b diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/refs/remotes/origin/master new/cosmic-comp-1.7.0/.git/refs/remotes/origin/master --- old/cosmic-comp-1.6.0/.git/refs/remotes/origin/master 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/refs/remotes/origin/master 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +5c93094574b31800caaa27255caac4a7253f24ad diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/refs/remotes/origin/testing-cosmic-comp-pr2656_noble new/cosmic-comp-1.7.0/.git/refs/remotes/origin/testing-cosmic-comp-pr2656_noble --- old/cosmic-comp-1.6.0/.git/refs/remotes/origin/testing-cosmic-comp-pr2656_noble 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/refs/remotes/origin/testing-cosmic-comp-pr2656_noble 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +5505b25c81e38331902cca620d78ac2cac34badb diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/refs/remotes/origin/update-smithay_noble new/cosmic-comp-1.7.0/.git/refs/remotes/origin/update-smithay_noble --- old/cosmic-comp-1.6.0/.git/refs/remotes/origin/update-smithay_noble 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/refs/remotes/origin/update-smithay_noble 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +08b82773c077a31e62cac127e7e6c3319ce78df5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/.git/refs/tags/epoch-1.7.0 new/cosmic-comp-1.7.0/.git/refs/tags/epoch-1.7.0 --- old/cosmic-comp-1.6.0/.git/refs/tags/epoch-1.7.0 1970-01-01 01:00:00.000000000 +0100 +++ new/cosmic-comp-1.7.0/.git/refs/tags/epoch-1.7.0 2026-08-25 17:33:56.000000000 +0200 @@ -0,0 +1 @@ +c0b3192fd9cc979b84d58ce7b444145e32aa65f9 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/Cargo.lock new/cosmic-comp-1.7.0/Cargo.lock --- old/cosmic-comp-1.6.0/Cargo.lock 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/Cargo.lock 2026-08-25 17:33:56.000000000 +0200 @@ -817,6 +817,7 @@ "profiling", "rand 0.10.0", "regex", + "resvg 0.45.1", "ron 0.12.0", "rust-embed", "rustix 1.1.4", @@ -6893,9 +6894,9 @@ [[package]] name = "xcursor" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" [[package]] name = "xdg" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/Cargo.toml new/cosmic-comp-1.7.0/Cargo.toml --- old/cosmic-comp-1.6.0/Cargo.toml 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/Cargo.toml 2026-08-25 17:33:56.000000000 +0200 @@ -49,6 +49,7 @@ ordered-float = "5.1" png = "0.18" regex = "1" +resvg = "0.45" ron = "0.12" rust-embed = { version = "8.11", features = ["debug-embed"] } sanitize-filename = "0.6.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/cosmic-comp-config/src/lib.rs new/cosmic-comp-1.7.0/cosmic-comp-config/src/lib.rs --- old/cosmic-comp-1.6.0/cosmic-comp-config/src/lib.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/cosmic-comp-config/src/lib.rs 2026-08-25 17:33:56.000000000 +0200 @@ -101,6 +101,8 @@ pub appearance_settings: AppearanceConfig, /// Hide the cursor after this many seconds of pointer inactivity (None disables) pub cursor_hide_timeout: Option<u32>, + /// Briefly magnify the cursor when the pointer is shaken, to help locate it + pub cursor_shake_to_find: bool, pub activation_policy: ActivationPolicy, } @@ -139,6 +141,7 @@ accessibility_zoom: ZoomConfig::default(), appearance_settings: AppearanceConfig::default(), cursor_hide_timeout: None, + cursor_shake_to_find: true, activation_policy: ActivationPolicy::default(), } } @@ -195,6 +198,10 @@ pub enable_mouse_zoom_shortcuts: bool, } +impl ZoomConfig { + pub const ZOOM_INCREMENT_PRESETS: &[u32] = &[10, 25, 50, 75, 100, 150, 200]; +} + impl Default for ZoomConfig { fn default() -> Self { ZoomConfig { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/backend/render/cursor.rs new/cosmic-comp-1.7.0/src/backend/render/cursor.rs --- old/cosmic-comp-1.6.0/src/backend/render/cursor.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/backend/render/cursor.rs 2026-08-25 17:33:56.000000000 +0200 @@ -8,6 +8,9 @@ utils::prelude::*, wayland::handlers::compositor::FRAME_TIME_FILTER, }; +use keyframe::{ease, functions::EaseInOutCubic}; +use resvg::{tiny_skia, usvg}; +use serde::Deserialize; use smithay::{ backend::{ allocator::Fourcc, @@ -38,7 +41,7 @@ wayland::compositor::{get_role, with_states}, }; use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, io::Read, sync::Mutex, time::{Duration, Instant}, @@ -51,14 +54,43 @@ static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba"); +/// A single frame of a scalable SVG cursor: the parsed SVG plus its metadata. +#[derive(Debug, Clone)] +struct SvgFrame { + tree: usvg::Tree, + /// The nominal (logical) size the SVG is authored for. + nominal_size: f32, + /// Hotspot coordinates, in the SVG's own (nominal) coordinate space. + hotspot_x: f32, + hotspot_y: f32, + /// Delay to the next frame in milliseconds (0 for static cursors). + delay: u32, +} + +#[derive(Debug, Clone)] +enum CursorKind { + /// Legacy raster XCursor frames. + Xcursor(Vec<Image>), + /// Scalable SVG frames, rasterized on demand. + Svg(Vec<SvgFrame>), +} + #[derive(Debug, Clone)] pub struct Cursor { - icons: Vec<Image>, + kind: CursorKind, size: u32, } impl Cursor { pub fn load(theme: &CursorTheme, shape: CursorIcon, size: u32) -> Cursor { + // Prefer a scalable SVG cursor when the theme provides one. + if let Some(frames) = load_svg_icon(theme, shape) { + return Cursor { + kind: CursorKind::Svg(frames), + size, + }; + } + let icons = load_icon(theme, shape) .map_err(|err| warn!(?err, "Unable to load xcursor, using fallback cursor")) .or_else(|_| load_icon(theme, CursorIcon::Default)) @@ -75,44 +107,134 @@ }] }); - Cursor { icons, size } + Cursor { + kind: CursorKind::Xcursor(icons), + size, + } } pub fn get_image(&self, scale: u32, millis: u32) -> Image { let size = self.size * scale; - frame(millis, size, &self.icons) + let idx = self.frame_index(size, millis); + self.render_frame(size, idx) + } + + /// Selects the index of the frame to display at nominal size `size` (in px) + /// and elapsed `millis`. + fn frame_index(&self, size: u32, millis: u32) -> usize { + match &self.kind { + CursorKind::Xcursor(images) => xcursor_frame_index(millis, size, images), + CursorKind::Svg(frames) => svg_frame_index(millis, frames), + } + } + + /// Produces the RGBA image for frame `idx`, rasterizing SVG cursors at the + /// requested nominal size `size` (in px). + fn render_frame(&self, size: u32, idx: usize) -> Image { + match &self.kind { + CursorKind::Xcursor(images) => images[idx].clone(), + CursorKind::Svg(frames) => rasterize_svg_frame(&frames[idx], size), + } } } -fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> { - // Follow the nominal size of the cursor to choose the nearest +/// Rasterize a scalable cursor frame at nominal pixel size `size`. Per the KDE +/// SVG cursor format, the SVG canvas and hotspot are scaled by +/// `size / nominal_size`. The returned [`Image`] uses the same premultiplied +/// byte order (BGRA / little-endian ARGB) as xcursor images. +fn rasterize_svg_frame(frame: &SvgFrame, size: u32) -> Image { + let factor = size as f32 / frame.nominal_size; + let svg_size = frame.tree.size(); + let width = ((svg_size.width() * factor).floor() as u32).max(1); + let height = ((svg_size.height() * factor).floor() as u32).max(1); + + let mut image = Image { + size, + width, + height, + xhot: (frame.hotspot_x * factor).floor() as u32, + yhot: (frame.hotspot_y * factor).floor() as u32, + delay: frame.delay, + pixels_rgba: Vec::new(), + pixels_argb: Vec::new(), // unused + }; + + match tiny_skia::Pixmap::new(width, height) { + Some(mut pixmap) => { + resvg::render( + &frame.tree, + tiny_skia::Transform::from_scale(factor, factor), + &mut pixmap.as_mut(), + ); + // tiny-skia produces premultiplied RGBA; xcursor images consume the + // raw little-endian ARGB byte order (premultiplied BGRA). Swap R<->B. + let mut pixels = pixmap.take(); + for px in pixels.as_chunks_mut::<4>().0 { + px.swap(0, 2); + } + image.pixels_rgba = pixels; + } + None => { + warn!(width, height, "Failed to allocate cursor pixmap"); + image.pixels_rgba = vec![0; (width as usize) * (height as usize) * 4]; + } + } + + image +} + +/// Indices (into `images`) of all frames sharing the resolution nearest to `size`. +fn nearest_image_indices(size: u32, images: &[Image]) -> Vec<usize> { + // Follow the nominal size of the cursor to choose the nearest. let nearest_image = images .iter() .min_by_key(|image| u32::abs_diff(size, image.size)) .unwrap(); + let (width, height) = (nearest_image.width, nearest_image.height); - images.iter().filter(move |image| { - image.width == nearest_image.width && image.height == nearest_image.height - }) + images + .iter() + .enumerate() + .filter(|(_, image)| image.width == width && image.height == height) + .map(|(i, _)| i) + .collect() } -fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image { - let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay); +fn xcursor_frame_index(mut millis: u32, size: u32, images: &[Image]) -> usize { + let indices = nearest_image_indices(size, images); + let total: u32 = indices.iter().map(|&i| images[i].delay).sum(); if total == 0 { - millis = 0; - } else { - millis %= total; + return indices[0]; } + millis %= total; - for img in nearest_images(size, images) { - if millis <= img.delay { - return img.clone(); + for &i in &indices { + if millis <= images[i].delay { + return i; } - millis -= img.delay; + millis -= images[i].delay; } - unreachable!() + *indices.last().unwrap() +} + +fn svg_frame_index(mut millis: u32, frames: &[SvgFrame]) -> usize { + let total: u32 = frames.iter().map(|frame| frame.delay).sum(); + + if total == 0 { + return 0; + } + millis %= total; + + for (i, frame) in frames.iter().enumerate() { + if millis <= frame.delay { + return i; + } + millis -= frame.delay; + } + + frames.len() - 1 } #[derive(thiserror::Error, Debug)] @@ -174,6 +296,79 @@ Err(Error::NoDefaultCursor) } +/// A frame entry in a `cursors_scalable/<shape>/metadata.json` file, per the +/// KDE SVG cursor format specification. +#[derive(Debug, Clone, Deserialize)] +struct SvgCursorMeta { + filename: String, + nominal_size: f32, + hotspot_x: f32, + hotspot_y: f32, + /// Only present for animated cursors; defaults to 0 for static ones. + #[serde(default)] + delay: u32, +} + +/// Resolves a scalable (SVG) cursor for `shape` +fn load_svg_icon(theme: &CursorTheme, shape: CursorIcon) -> Option<Vec<SvgFrame>> { + let shape_name = shape.to_string(); + let options = usvg::Options::default(); + for name in cursor_aliases(&shape_name) + .iter() + .copied() + .chain(std::iter::once(shape_name.as_str())) + { + if let Some(dir) = theme.load_scalable(name) + && let Some(frames) = parse_svg_dir(&dir, &options) + { + return Some(frames); + } + } + + None +} + +/// Reads a `cursors_scalable/<shape>` directory (its `metadata.json` and the +/// referenced SVG files) into a list of parsed frames. +fn parse_svg_dir(dir: &std::path::Path, options: &usvg::Options) -> Option<Vec<SvgFrame>> { + let metadata = std::fs::read(dir.join("metadata.json")).ok()?; + let metas: Vec<SvgCursorMeta> = match serde_json::from_slice(&metadata) { + Ok(metas) => metas, + Err(err) => { + warn!(?dir, ?err, "Malformed SVG cursor metadata"); + return None; + } + }; + + let mut frames = Vec::with_capacity(metas.len()); + for meta in metas { + let svg_path = dir.join(&meta.filename); + let svg_data = match std::fs::read(&svg_path) { + Ok(data) => data, + Err(err) => { + warn!(?svg_path, ?err, "Unable to read SVG cursor"); + break; + } + }; + let tree = match usvg::Tree::from_data(&svg_data, options) { + Ok(tree) => tree, + Err(err) => { + warn!(?svg_path, ?err, "Unable to parse SVG cursor"); + break; + } + }; + frames.push(SvgFrame { + tree, + nominal_size: meta.nominal_size, + hotspot_x: meta.hotspot_x, + hotspot_y: meta.hotspot_y, + delay: meta.delay, + }); + } + + (!frames.is_empty()).then_some(frames) +} + render_elements! { pub CursorRenderElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send; Static=MemoryRenderBufferRenderElement<R>, @@ -265,11 +460,62 @@ cursors: HashMap<CursorIcon, Cursor>, current_image: Option<Image>, - image_cache: Vec<(Image, MemoryRenderBuffer)>, + image_cache: Vec<CachedFrame>, hidden: bool, idle_timer: Option<RegistrationToken>, last_armed: Option<Instant>, + + // shake-to-find + shake_path: VecDeque<PathSample>, + shake_path_position: Point<f64, Logical>, + magnify_until: Option<Instant>, + magnify_target: f32, + magnification: f32, + anim_from: f32, + anim_start: Option<Instant>, + rest_started: Option<Instant>, +} + +/// A rasterized cursor frame, keyed by `(shape, pixel size, frame index)`. +struct CachedFrame { + key: (CursorIcon, u32, usize), + image: Image, + buffer: MemoryRenderBuffer, + unmagnified: bool, +} + +/// One sampled pointer position on the recent motion path. +#[derive(Clone, Copy)] +struct PathSample { + position: Point<f64, Logical>, + time: Instant, +} + +/// How long everything must stay unmagnified before the enlarged frames go. +const MAGNIFIED_FRAME_GRACE: Duration = Duration::from_secs(10); + +/// How far back the motion path is considered when looking for a shake. +const SHAKE_INTERVAL: Duration = Duration::from_millis(1000); +/// Path-length / bounding-box-diagonal ratio required to count as a shake. +const SHAKE_SENSITIVITY: f64 = 4.0; +/// Minimum bounding-box diagonal (logical px) before a shake is considered. +const SHAKE_DIAGONAL_MIN: f64 = 100.0; +/// Two deltas count as "the same direction" if both lie within this tolerance. +const SHAKE_SAME_SIGN_TOLERANCE: f64 = 1.0; +/// Keep the cursor enlarged for this long after the last detected shake. +const SHAKE_HOLD: Duration = Duration::from_millis(2000); +/// Extra magnification added by each shake, growing from the normal cursor size. +const OVER_MAGNIFICATION: f32 = 1.0; +/// Upper bound on the nominal size (in px) a cursor frame is rasterized at. +const MAX_RASTER_SIZE: u32 = 512; +/// Duration of the grow/shrink animation. +const MAGNIFICATION_ANIM: Duration = Duration::from_millis(200); + +/// small movement is ignored and direction stays the same +fn same_direction(a: f64, b: f64) -> bool { + (a >= -SHAKE_SAME_SIGN_TOLERANCE && b >= -SHAKE_SAME_SIGN_TOLERANCE) + || (a <= SHAKE_SAME_SIGN_TOLERANCE && b <= SHAKE_SAME_SIGN_TOLERANCE) } impl CursorStateInner { @@ -282,14 +528,141 @@ } pub fn get_named_cursor(&mut self, shape: CursorIcon) -> &Cursor { + let cursor_theme = &self.cursor_theme; + let cursor_size = self.cursor_size; self.cursors .entry(shape) - .or_insert_with(|| Cursor::load(&self.cursor_theme, shape, self.cursor_size)) + .or_insert_with(|| Cursor::load(cursor_theme, shape, cursor_size)) } pub fn size(&self) -> u32 { self.cursor_size } + + /// Drop the rasterizations only a magnified cursor needed, once nothing has + /// magnified it for [`MAGNIFIED_FRAME_GRACE`]. + pub fn drop_magnified_frames(&mut self, now: Instant, zoomed: bool) { + if zoomed || self.is_magnifying() { + self.rest_started = None; + return; + } + let rest_started = *self.rest_started.get_or_insert(now); + if now.duration_since(rest_started) < MAGNIFIED_FRAME_GRACE { + return; + } + + self.image_cache.retain(|frame| frame.unmagnified); + } + + /// Feed one relative-motion event into the shake detector. + pub fn detect_shake(&mut self, delta: Point<f64, Logical>, now: Instant) { + // Drop samples that have aged out of the time window. + while let Some(oldest) = self.shake_path.front() { + if now.duration_since(oldest.time) >= SHAKE_INTERVAL { + self.shake_path.pop_front(); + } else { + break; + } + } + + if delta.x != 0.0 || delta.y != 0.0 { + self.shake_path_position += delta; + let sample = PathSample { + position: self.shake_path_position, + time: now, + }; + + if self.shake_path.len() >= 2 { + let last = self.shake_path[self.shake_path.len() - 1].position; + let prev = self.shake_path[self.shake_path.len() - 2].position; + let last_delta = last - prev; + if same_direction(last_delta.x, delta.x) && same_direction(last_delta.y, delta.y) { + *self.shake_path.back_mut().unwrap() = sample; + } else { + self.shake_path.push_back(sample); + } + } else { + self.shake_path.push_back(sample); + } + } + + if self.shake_path.len() < 2 { + return; + } + + let first = self.shake_path[0].position; + let (mut left, mut top, mut right, mut bottom) = (first.x, first.y, first.x, first.y); + let mut path_length = 0.0; + for i in 1..self.shake_path.len() { + let p = self.shake_path[i].position; + left = left.min(p.x); + top = top.min(p.y); + right = right.max(p.x); + bottom = bottom.max(p.y); + + let step = p - self.shake_path[i - 1].position; + path_length += step.x.hypot(step.y); + } + + let diagonal = (right - left).hypot(bottom - top); + if diagonal < SHAKE_DIAGONAL_MIN { + return; + } + + // Path noticeably longer than the diagonal => a shake gesture. + if path_length / diagonal > SHAKE_SENSITIVITY { + self.grow(now); + self.shake_path.clear(); + } + } + + /// grow the cursor by one more increment (unbounded) + fn grow(&mut self, now: Instant) { + self.animate_to(self.magnify_target + OVER_MAGNIFICATION, now); + self.magnify_until = Some(now + SHAKE_HOLD); + } + + /// Start a 200ms `InOutCubic` tween from the current size to `target`. + fn animate_to(&mut self, target: f32, now: Instant) { + if (target - self.magnify_target).abs() < f32::EPSILON { + return; + } + self.anim_from = self.magnification; + self.anim_start = Some(now); + self.magnify_target = target; + } + + /// Advance the magnification animation and return the current factor. + pub fn animated_magnification(&mut self, now: Instant) -> f32 { + // Begin shrinking back once the hold window elapses. + if let Some(until) = self.magnify_until + && now >= until + { + self.magnify_until = None; + self.animate_to(1.0, now); + } + + self.magnification = match self.anim_start { + Some(start) => { + // `ease` clamps the time to `0.0..=1.0` for us. + let t = now.duration_since(start).as_secs_f32() / MAGNIFICATION_ANIM.as_secs_f32(); + if t >= 1.0 { + self.anim_start = None; + } + ease(EaseInOutCubic, self.anim_from, self.magnify_target, t) + } + None => self.magnify_target, + }; + self.magnification + } + + /// Whether the cursor is currently magnified or pending; drives continued redraws. + pub fn is_magnifying(&self) -> bool { + self.magnify_until.is_some() + || self.anim_start.is_some() + || self.magnification > 1.001 + || self.magnify_target > 1.001 + } } pub fn load_cursor_env() -> (String, u32) { @@ -324,10 +697,34 @@ hidden: false, idle_timer: None, last_armed: None, + + shake_path: VecDeque::new(), + shake_path_position: Point::from((0.0, 0.0)), + magnify_until: None, + magnify_target: 1.0, + magnification: 1.0, + anim_from: 1.0, + anim_start: None, + rest_started: None, } } } +/// Pick the size a cursor frame is rasterized at, given the size the output wants +/// (`needed`) and the size it would want unmagnified (`base`). +/// +/// Rasterizations are restricted to `base * 2^n`, rounded up, and clamped to +/// [`MAX_RASTER_SIZE`]. +fn raster_size(needed: u32, base: u32) -> u32 { + let base = base.max(1); + let cap = MAX_RASTER_SIZE.max(base); + let mut rung = base; + while rung < needed && rung.saturating_mul(2) <= cap { + rung *= 2; + } + rung +} + #[profiling::function] pub fn draw_cursor<R>( renderer: &mut R, @@ -363,32 +760,53 @@ return; } - let integer_scale = (scale.x.max(scale.y) * buffer_scale).ceil() as u32; - let frame = state - .get_named_cursor(current_cursor) - .get_image(integer_scale, time.as_millis()); - let actual_scale = (frame.size / state.size()).max(1); + let output_scale = scale.x.max(scale.y); + let integer_scale = (output_scale * buffer_scale).ceil() as u32; + let unmagnified_px = state.size() * (output_scale.ceil() as u32); + let size_px = raster_size(state.size() * integer_scale, unmagnified_px); + + // Pick the frame to display without rasterizing, so a cache hit avoids + // any SVG rendering. The `&Cursor` borrow is scoped to this block. + let frame_idx = { + let cursor = state.get_named_cursor(current_cursor); + cursor.frame_index(size_px, time.as_millis()) + }; + let key = (current_cursor, size_px, frame_idx); - let pointer_images = &mut state.image_cache; - let maybe_image = pointer_images - .iter() - .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }); - let pointer_image = match maybe_image { - Some(image) => image, + // Rasterize and upload this (shape, size, frame) only if not cached. + let index = match state.image_cache.iter().position(|frame| frame.key == key) { + Some(index) => index, None => { + let image = { + let cursor = state.get_named_cursor(current_cursor); + cursor.render_frame(size_px, frame_idx) + }; + let actual_scale = (image.size / state.size()).max(1); let buffer = MemoryRenderBuffer::from_slice( - &frame.pixels_rgba, + &image.pixels_rgba, Fourcc::Argb8888, - (frame.width as i32, frame.height as i32), + (image.width as i32, image.height as i32), actual_scale as i32, Transform::Normal, None, ); - pointer_images.push((frame.clone(), buffer)); - pointer_images.last().map(|(_, i)| i).unwrap() + state.image_cache.push(CachedFrame { + key, + image, + buffer, + unmagnified: size_px == unmagnified_px, + }); + state.image_cache.len() - 1 } }; + let (frame, pointer_image) = { + let entry = &mut state.image_cache[index]; + entry.unmagnified |= size_px == unmagnified_px; + (entry.image.clone(), entry.buffer.clone()) + }; + let actual_scale = (frame.size / state.size()).max(1); + let hotspot = Point::<i32, BufferCoords>::from((frame.xhot as i32, frame.yhot as i32)) .to_logical( actual_scale as i32, @@ -402,7 +820,7 @@ MemoryRenderBufferRenderElement::from_buffer( renderer, location.to_physical(scale), - pointer_image, + &pointer_image, None, None, None, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/backend/render/element.rs new/cosmic-comp-1.7.0/src/backend/render/element.rs --- old/cosmic-comp-1.6.0/src/backend/render/element.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/backend/render/element.rs 2026-08-25 17:33:56.000000000 +0200 @@ -41,7 +41,9 @@ Workspace( RelocateRenderElement<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>, ), - Cursor(RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>), + Cursor( + RescaleRenderElement<RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>>, + ), Dnd(SurfaceRenderElement<R>), MoveGrab(RescaleRenderElement<CosmicMappedRenderElement<R>>), Postprocess( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/backend/render/mod.rs new/cosmic-comp-1.7.0/src/backend/render/mod.rs --- old/cosmic-comp-1.6.0/src/backend/render/mod.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/backend/render/mod.rs 2026-08-25 17:33:56.000000000 +0200 @@ -510,22 +510,35 @@ }; let location = pointer.current_location() - output.current_location().to_f64(); + // Shake-to-find magnification, applied around the pointer tip. + let cursor_magnification = seat + .user_data() + .get::<cursor::CursorState>() + .map_or(1.0, |s| { + s.lock().unwrap().animated_magnification(Instant::now()) + }); + let cursor_center = location.to_physical(scale).to_i32_round(); + if mode != CursorMode::None { cursor::draw_cursor( renderer, seat, location, scale.into(), - zoom_scale, + zoom_scale * cursor_magnification as f64, now, blur_strength, mode != CursorMode::NotDefault, &mut |elem, hotspot| { push(CosmicElement::Cursor(RescaleRenderElement::from_element( - RelocateRenderElement::from_element( - elem, - Point::from((-hotspot.x, -hotspot.y)), - Relocate::Relative, + RescaleRenderElement::from_element( + RelocateRenderElement::from_element( + elem, + Point::from((-hotspot.x, -hotspot.y)), + Relocate::Relative, + ), + cursor_center, + cursor_magnification as f64, ), focal_point .as_logical() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/config/mod.rs new/cosmic-comp-1.7.0/src/config/mod.rs --- old/cosmic-comp-1.6.0/src/config/mod.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/config/mod.rs 2026-08-25 17:33:56.000000000 +0200 @@ -969,6 +969,10 @@ } } } + "cursor_shake_to_find" => { + let new = get_config::<bool>(&config, "cursor_shake_to_find"); + state.common.config.cosmic_conf.cursor_shake_to_find = new; + } "cursor_hide_timeout" => { let new = get_config::<Option<u32>>(&config, "cursor_hide_timeout"); if new != state.common.config.cosmic_conf.cursor_hide_timeout { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/input/actions.rs new/cosmic-comp-1.7.0/src/input/actions.rs --- old/cosmic-comp-1.6.0/src/input/actions.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/input/actions.rs 2026-08-25 17:33:56.000000000 +0200 @@ -29,6 +29,8 @@ use super::gestures; +const MAX_ZOOM: f64 = 256.0; + fn propagate_by_default(action: &shortcuts::Action) -> bool { matches!( action, @@ -1116,7 +1118,14 @@ } if zoom_seat == *seat { - let new_level = (current_level + change).max(1.0); + let factor = 1.0 + change.abs(); + let new_level = if change < 0. { + current_level / factor + } else { + current_level * factor + } + .clamp(1.0, MAX_ZOOM); + let new_level = if new_level < 1.01 { 1.0 } else { new_level }; shell.trigger_zoom( seat, Some(&output), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/input/mod.rs new/cosmic-comp-1.7.0/src/input/mod.rs --- old/cosmic-comp-1.6.0/src/input/mod.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/input/mod.rs 2026-08-25 17:33:56.000000000 +0200 @@ -337,6 +337,21 @@ notify_cursor_activity(self, &seat); let current_output = seat.active_output(); + if self.common.config.cosmic_conf.cursor_shake_to_find + && let Some(cursor_state) = + seat.user_data() + .get::<crate::backend::render::cursor::CursorState>() + { + let active = { + let mut cursor = cursor_state.lock().unwrap(); + cursor.detect_shake(event.delta(), std::time::Instant::now()); + cursor.is_magnifying() + }; + if active { + self.backend.schedule_render(¤t_output); + } + } + let mut position = seat.get_pointer().unwrap().current_location().as_global(); let under = State::surface_under(position, ¤t_output, &shell) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/shell/mod.rs new/cosmic-comp-1.7.0/src/shell/mod.rs --- old/cosmic-comp-1.6.0/src/shell/mod.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/shell/mod.rs 2026-08-25 17:33:56.000000000 +0200 @@ -1614,6 +1614,29 @@ a11y_keyboard_monitor.refresh(); } self.image_copy_capture_state.cleanup(); + self.cleanup_cursor_images(); + } + + /// Release the enlarged cursor frames a finished shake or zoom left behind. + fn cleanup_cursor_images(&mut self) { + let shell = self.shell.read(); + let zoomed = shell.zoom_state.as_ref().is_some_and(|zoom_state| { + shell + .outputs() + .any(|output| zoom_state.animating_level(output) > 1.0) + }); + let now = Instant::now(); + for seat in shell.seats.iter() { + if let Some(cursor_state) = seat + .user_data() + .get::<crate::backend::render::cursor::CursorState>() + { + cursor_state + .lock() + .unwrap() + .drop_magnified_frames(now, zoomed); + } + } } pub fn refresh_idle_inhibit(&mut self) { @@ -2286,6 +2309,11 @@ .is_some_and(|state| state.lock().unwrap().is_animating()) }) }) + || self.seats.iter().any(|seat| { + seat.user_data() + .get::<crate::backend::render::cursor::CursorState>() + .is_some_and(|state| state.lock().unwrap().is_magnifying()) + }) } pub fn update_animations(&mut self) -> HashMap<ClientId, Client> { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/shell/zoom.rs new/cosmic-comp-1.7.0/src/shell/zoom.rs --- old/cosmic-comp-1.6.0/src/shell/zoom.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/shell/zoom.rs 2026-08-25 17:33:56.000000000 +0200 @@ -7,7 +7,7 @@ theme, widget::{self, icon::Named}, }; -use cosmic_comp_config::ZoomMovement; +use cosmic_comp_config::{ZoomConfig, ZoomMovement}; use cosmic_config::ConfigSet; use keyframe::{ease, functions::Linear}; use smithay::{ @@ -447,7 +447,7 @@ impl ZoomProgram { pub fn new(level: f64, movement: ZoomMovement, increment: u32) -> Self { - let mut increments = vec![25, 50, 100, 150, 200]; + let mut increments = ZoomConfig::ZOOM_INCREMENT_PRESETS.to_vec(); if !increments.contains(&increment) { increments.push(increment); } @@ -828,7 +828,7 @@ if let Some(pos) = self.increments.iter().position(|val| *val == increment) { self.increment_idx = pos; } else { - let mut increments = vec![25, 50, 100, 150, 200]; + let mut increments = ZoomConfig::ZOOM_INCREMENT_PRESETS.to_vec(); if !increments.contains(&increment) { increments.push(increment); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cosmic-comp-1.6.0/src/wayland/handlers/session_lock.rs new/cosmic-comp-1.7.0/src/wayland/handlers/session_lock.rs --- old/cosmic-comp-1.6.0/src/wayland/handlers/session_lock.rs 2026-08-18 13:26:18.000000000 +0200 +++ new/cosmic-comp-1.7.0/src/wayland/handlers/session_lock.rs 2026-08-25 17:33:56.000000000 +0200 @@ -46,6 +46,11 @@ let mut shell = self.common.shell.write(); shell.session_lock = None; + let seats = shell.seats.iter().cloned().collect::<Vec<_>>(); + for seat in &seats { + self.common.idle_notifier_state.notify_activity(seat); + } + for output in shell.outputs() { self.backend.schedule_render(output); } ++++++ cosmic-comp.obsinfo ++++++ --- /var/tmp/diff_new_pack.3QF0Lf/_old 2026-08-28 20:01:01.327585083 +0200 +++ /var/tmp/diff_new_pack.3QF0Lf/_new 2026-08-28 20:01:01.336585398 +0200 @@ -1,5 +1,5 @@ name: cosmic-comp -version: 1.6.0 -mtime: 1787052378 -commit: 314fc670a83f1e570b069cd709df809ba99fa334 +version: 1.7.0 +mtime: 1787672036 +commit: 31827ed2409f92d3bc224d3ce601c6f2136183fb ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/cosmic-comp/vendor.tar.zst /work/SRC/openSUSE:Factory/.cosmic-comp.new.1265/vendor.tar.zst differ: char 7, line 1
