aboutsummaryrefslogtreecommitdiffstats
path: root/gtk3
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2021-05-29 16:12:00 +0900
committerKazuki Yamaguchi <k@rhe.jp>2021-05-29 16:12:00 +0900
commit283005b7ce9f7f5c4984cbc5e3544e8f8178d027 (patch)
treeed3c768d35429d04ed1e80013499f8abd6a2ff98 /gtk3
parentd5e6193f37335e82fcc41c72e873dadede89daab (diff)
downloadwf-clock-283005b7ce9f7f5c4984cbc5e3544e8f8178d027.tar.gz
x11: make ruby-gtk3 version more usable
Diffstat (limited to 'gtk3')
-rw-r--r--gtk3/Gemfile1
-rw-r--r--gtk3/README24
l---------gtk3/TrayIcon.ico1
-rwxr-xr-xgtk3/wf-clock131
4 files changed, 151 insertions, 6 deletions
diff --git a/gtk3/Gemfile b/gtk3/Gemfile
index e338598..62c6a76 100644
--- a/gtk3/Gemfile
+++ b/gtk3/Gemfile
@@ -2,3 +2,4 @@ source "https://rubygems.org"
gem "rake"
gem "gtk3"
+gem "xlib"
diff --git a/gtk3/README b/gtk3/README
index 980b1d7..0ca9f1c 100644
--- a/gtk3/README
+++ b/gtk3/README
@@ -1,16 +1,30 @@
wf-clock
========
-wf-clock implemented using Ruby/GNOME. Ruby and Bundler must be installed.
+wf-clock for X11 users, implemented using Ruby/GNOME.
-Usage:
+It tries to find the Warframe main window and place itself at bottom-left of
+the window.
+
+It currently does not check if the game has input focus. In other words, when
+another window is overlapping the game, the overlay still shows top-most.
+
+There is no configuration file. Edit the source code to make any changes.
+
+Prerequisites
+-------------
+
+ - Ruby
+ - Bundler
+
+gtk3/libffi headers may also be required to compile native extensions.
+
+Usage
+-----
$ bundle install
$ ./wf-clock
-Unlike the Windows version, there is currently no configuration. Edit the
-program directly to make any changes.
-
You also probably have to configure your window manager to disable borders,
shadows, window titles, background blue, etc., for windows with role
'no-border-pop-up'. I use the following settings on my computer.
diff --git a/gtk3/TrayIcon.ico b/gtk3/TrayIcon.ico
new file mode 120000
index 0000000..c26e2eb
--- /dev/null
+++ b/gtk3/TrayIcon.ico
@@ -0,0 +1 @@
+../WarframeClock/Resources/TrayIcon.ico \ No newline at end of file
diff --git a/gtk3/wf-clock b/gtk3/wf-clock
index 8e26f3e..46a8474 100755
--- a/gtk3/wf-clock
+++ b/gtk3/wf-clock
@@ -3,6 +3,7 @@ require "bundler/setup"
require "net/http"
require "json"
require "gtk3"
+require "xlib"
CETUS_EPOCH = 1521369472.764
CETUS_LEN = 8998.87481
@@ -23,6 +24,7 @@ window.keep_above = true
window.resizable = false
window.role = "no-border-pop-up"
window.app_paintable = true
+window.skip_taskbar_hint = true
window.visual = Gdk::Screen.default.rgba_visual
box = Gtk::Box.new(:horizontal, 0)
@@ -32,9 +34,136 @@ label = Gtk::Label.new
label.markup = "NOT READY YET"
box.add(label)
+menu = Gtk::Menu.new
+menu.append(Gtk::MenuItem.new.tap { |m|
+ m.label = "Quit"
+ m.signal_connect("activate") { exit 0 }
+})
+menu.show_all
+
+icon = Gtk::StatusIcon.new
+icon.file = File.join(__dir__, "TrayIcon.ico")
+icon.tooltip_text = "wf-clock"
+icon.signal_connect("popup-menu") { |tray, button, time|
+ menu.popup(nil, nil, button, time)
+}
+
window.resize(box.width_request, box.height_request)
+window.iconify # Hidden by default
window.show_all
+# Create a thread to watch Warframe's main window
+Thread.start do
+ display = Xlib::Display.new(Xlib.XOpenDisplay(ENV["DISPLAY"]))
+ root_xwindow = Xlib.XRootWindowOfScreen(Xlib::Screen.new(display[:screens]))
+
+ walk_xwindows = -> (xwindow = nil, &blk) do
+ blk.(xwindow ||= root_xwindow)
+
+ dummy = FFI::MemoryPointer.new(:Window)
+ children = FFI::MemoryPointer.new(:pointer)
+ nchildren = FFI::MemoryPointer.new(:int)
+ status = Xlib.XQueryTree(display, xwindow, dummy, dummy, children, nchildren)
+ raise "XQueryTree() failed" if status == 0
+
+ ptr = children.read_pointer
+ nchildren.get_int(0).times { |n|
+ walk_xwindows.(ptr.get_ulong(0), &blk)
+ ptr += 8
+ }
+ ensure
+ children and Xlib.XFree(children.read_pointer)
+ end
+
+ xwindow_bound = -> xwindow {
+ attrs = Xlib::WindowAttributes.new
+ Xlib.XGetWindowAttributes(display, xwindow, attrs)
+
+ x_abs = FFI::MemoryPointer.new(:int)
+ y_abs = FFI::MemoryPointer.new(:int)
+ child = FFI::MemoryPointer.new(:Window)
+
+ Xlib.XTranslateCoordinates(display, xwindow, root_xwindow,
+ 0, 0, x_abs, y_abs, child)
+ [x_abs.read_int, y_abs.read_int, attrs[:width], attrs[:height]]
+ }
+
+ xwindow_is_hidden = -> xwindow {
+ begin
+ wm_state_atom = Xlib.XInternAtom(display, "_NET_WM_STATE", false)
+ wm_state_hidden_atom = Xlib.XInternAtom(display, "_NET_WM_STATE_HIDDEN", false)
+ type = FFI::MemoryPointer.new(:Atom)
+ format = FFI::MemoryPointer.new(:int)
+ nitems = FFI::MemoryPointer.new(:ulong)
+ after = FFI::MemoryPointer.new(:ulong)
+ data = FFI::MemoryPointer.new(:pointer)
+ status = Xlib.XGetWindowProperty(display, xwindow, wm_state_atom,
+ 0, 65536, false, Xlib::AnyPropertyType,
+ type, format, nitems, after, data)
+ if status != 0
+ warn "XGetWindowProperty(_NET_WM_STATE) failed for xwindow #{xwindow}"
+ return false
+ end
+
+ nitems.read_ulong.times.any? { |i|
+ r = data.read_pointer + (i * 8)
+ r.read_ulong == wm_state_hidden_atom
+ }
+ ensure
+ data and Xlib.XFree(data.read_pointer)
+ end
+ }
+
+ wf_xwindow = -> () {
+ walk_xwindows.call { |xwindow|
+ # Warframe's main window should have the following properties:
+ # - WM_ICON_NAME(STRING) = "Warframe"
+ # - WM_NAME(STRING) = "Warframe"
+
+ match = [
+ ["_NET_WM_NAME", "Warframe"],
+ ["WM_ICON_NAME", "Warframe"],
+ ].all? { |name, expected|
+ begin
+ name_atom = Xlib.XInternAtom(display, name, false)
+ type = FFI::MemoryPointer.new(:Atom)
+ format = FFI::MemoryPointer.new(:int)
+ nitems = FFI::MemoryPointer.new(:ulong)
+ after = FFI::MemoryPointer.new(:ulong)
+ data = FFI::MemoryPointer.new(:pointer)
+ status = Xlib.XGetWindowProperty(display, xwindow, name_atom,
+ 0, 65536, false, Xlib::AnyPropertyType,
+ type, format, nitems, after, data)
+ if status != 0 || data.read_pointer.null?
+ # warn "XGetWindowProperty(#{name}) failed for xwindow #{xwindow}"
+ next
+ end
+
+ expected == data.read_pointer.read_string
+ ensure
+ data and Xlib.XFree(data.read_pointer)
+ end
+ }
+
+ return xwindow if match
+ }
+ nil
+ }
+
+ while true
+ xwin = wf_xwindow.()
+ if !xwin || xwindow_is_hidden.(xwin)
+ window.iconify
+ else
+ x, y, w, h = xwindow_bound.(xwin)
+ margin = 20
+ window.deiconify
+ window.move(x + margin, y + h - window.size[1] - margin)
+ end
+ sleep 3
+ end
+end
+
# And then, create a backgrund thread to update the content
Thread.start do
loop do
@@ -49,7 +178,7 @@ Thread.start do
end
label.markup = %{<span font_desc="Sans 48" foreground="white">#{text}</span>}
- sleep 1
+ sleep 0.5
end
end