From 05052bbd1716c9cc469ea4af3eb0fc81e4b9679e Mon Sep 17 00:00:00 2001 From: phil294 Date: Thu, 20 Jul 2023 11:01:59 +0200 Subject: [PATCH] Fix unsetting Clipbord / Add internal `clip_wait` logic to setting and retrieving `Clipboard`, so that those accesses always succeed (or explicitly show an error) --- src/cmd/misc/clip-wait.cr | 28 +++++++++++++++++++++------ src/run/display/gtk.cr | 40 +++++++++++++++++++++++++++++++++++++++ src/run/runner.cr | 11 +---------- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/src/cmd/misc/clip-wait.cr b/src/cmd/misc/clip-wait.cr index 8743b4f..fb8b4a5 100644 --- a/src/cmd/misc/clip-wait.cr +++ b/src/cmd/misc/clip-wait.cr @@ -4,20 +4,36 @@ class Cmd::Misc::ClipWait < Cmd::Base def self.max_args; 1 end def self.sets_error_level; true end def run(thread, args) - timeout_sec = args[0]?.try &.to_f? - start = Time.monotonic + timeout = args[0]?.try &.to_f?.try &.seconds + success = ClipWait.clip_wait(thread.runner.display.gtk, timeout) do |clp| + ! clp.empty? + end + success ? "0" : "1" + end + + # Waits until the clipboard passes the *&block*. Increases the sleep time inbetween operations + # slightly exponentially, but sleeps max. 0.5 seconds. + # Returns `true` when the *&block* returns true or `false` when *timeout* was exceeded. + def self.clip_wait(gtk, timeout : Time::Span? = nil, &block : ::String -> Bool) # The following works great and low on performance, but it only checks for the *possibility* # to retrieve text, even when it's empty: # thread.runner.display.gtk.clipboard &.wait_is_text_available # So we need to resort to looping which you could also easily do with ahk code itself. back_off_wait = 5.milliseconds - while (txt = thread.runner.display.gtk.clipboard &.wait_for_text || "").empty? - if timeout_sec - return "1" if Time.monotonic - start > timeout_sec.seconds + start = Time.monotonic + loop do + txt = gtk.clipboard &.wait_for_text || "" + if yield(txt) + break + end + if timeout + if Time.monotonic - start > timeout + return false + end end sleep back_off_wait back_off_wait = ::Math.min(back_off_wait * 1.2, 0.5.seconds) end - "0" + true end end \ No newline at end of file diff --git a/src/run/display/gtk.cr b/src/run/display/gtk.cr index d7a9a83..fbe8510 100644 --- a/src/run/display/gtk.cr +++ b/src/run/display/gtk.cr @@ -54,6 +54,46 @@ module Run block.call(clip) end end + def set_clipboard(text : String) + if text.empty? + clipboard do |clip| + # clip.clear # Doesn't do anything + # Doesn't work for access from outside of ahkx11 itself, even though https://stackoverflow.com/q/2418487 says so. Maybe a Crystal GIR bug?: + # clip.set_text("", 0) + # Ugly but works: + clip.image = GdkPixbuf::Pixbuf.new + end + if ! Cmd::Misc::ClipWait.clip_wait(self, 2.seconds, &.empty?) + raise Run::RuntimeException.new "Unsetting Clipboard failed [1]" + end + # But even now that we have confirmation that it was unset, the clipboard is sometimes still + # in the process of being reported to the active window. Setting it to a text and then unsetting + # it again seems to resolve it somehow. All of this is necessary, putting a sleep of the same duration + # instead is not enough. + clipboard do |clip| + clip.set_text("_ahk_x11_clipboard_internal", 27) + clip.store + end + if ! Cmd::Misc::ClipWait.clip_wait(self, 2.seconds, &.==("_ahk_x11_clipboard_internal")) + raise Run::RuntimeException.new "Unsetting Clipboard failed [2]" + end + clipboard do |clip| + clip.set_text("", 0) + clip.store + end + if ! Cmd::Misc::ClipWait.clip_wait(self, 2.seconds, &.empty?) + raise Run::RuntimeException.new "Unsetting Clipboard failed [3]" + end + else + clipboard do |clip| + clip.set_text(text, text.size) + clip.store + end + if ! Cmd::Misc::ClipWait.clip_wait(self, 2.seconds, &.==(text)) + raise Run::RuntimeException.new "Setting Clipboard failed" + end + end + end # Can't use @[Flags] because some values are *not* strict 2^n enum MsgBoxOptions diff --git a/src/run/runner.cr b/src/run/runner.cr index 98e0112..2b46879 100644 --- a/src/run/runner.cr +++ b/src/run/runner.cr @@ -210,16 +210,7 @@ module Run down = var.downcase case down when "clipboard" - display.gtk.clipboard do |clip| - if value.empty? - # clip.clear # Doesn't do anything - # clip.set_text("", 0) # Doesn't work outside of ahkx11 itself, even though https://stackoverflow.com/q/2418487 says so. Maybe a Crystal GIR bug? - clip.image = GdkPixbuf::Pixbuf.new # Ugly but works - else - clip.set_text(value, value.size) - end - clip.store - end + display.gtk.set_clipboard(value) else return if @built_in_static_vars[down]? || get_global_built_in_computed_var(down) {% if ! flag?(:release) %}