diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2021-05-29 16:12:00 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2021-05-29 16:12:00 +0900 |
commit | 283005b7ce9f7f5c4984cbc5e3544e8f8178d027 (patch) | |
tree | ed3c768d35429d04ed1e80013499f8abd6a2ff98 /gtk3 | |
parent | d5e6193f37335e82fcc41c72e873dadede89daab (diff) | |
download | wf-clock-283005b7ce9f7f5c4984cbc5e3544e8f8178d027.tar.gz |
x11: make ruby-gtk3 version more usable
Diffstat (limited to 'gtk3')
-rw-r--r-- | gtk3/Gemfile | 1 | ||||
-rw-r--r-- | gtk3/README | 24 | ||||
l--------- | gtk3/TrayIcon.ico | 1 | ||||
-rwxr-xr-x | gtk3/wf-clock | 131 |
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 |