Awesome-WM: Spawn client on same tag as parent - awesome-wm

My goal is to let clients that have a parent spawn on the same tag as their parents. Clients w/o parents should spawn on the active tag (as usual).
My first approach is to connect a signal using client.connect_signal("manage", ...). However, I couldn't find a way to get the parent of a client or to check if it has a parent.
Thank you for taking a look at my problem!
Update 1: I found client:get_transient_for_matching (matcher), but the documentation is not very helpful.
Update 2: Thanks to Uli for the hint to use client::transient_for as an easier way to get the transient. Using
client.connect_signal("manage", function (c)
parent = c.transient_for
naughty.notify({ preset = naughty.config.presets.critical,
title = "Debug",
text = tostring(c.window) .. " " .. (parent and tostring(parent.window) or "") })
if parent then
-- move new client to same tag and screen as parent
tag = parent.first_tag
screen = parent.screen
c:move_to_tag(tag)
c:move_to_screen(screen)
end
end)
I tried to achieve my goals and added a simple debug output using notifications. Now, only a very few new clients actually have a transient that is not nil. E.g., spawning git gui from a terminal does not have a transient. However, I strongly believe it should (or I misunderstood what a transient is).
Ubuntu 20LTS, Awesome WM version 4.3-4, awesome-extra 2019021001

You are looking for c.transient_for. This contains the client object for the "parent" window or nil.

Related

How to use one key-binding to spawn tag-specific program in awesome-wm?

Introduction:
In my wrokflow, I am using tags for specific purposes (WEB, IDE, EMAIL, File Manager,>Terminal/Taskwarrior-tui/Timewarrior) and I want to stick to this. So whenever I go to tag 1 I want to have a browser there.
Problem: I would like to minimize the number of key bindings for the most important programs.
Question: How can I use one keybinding for launching a "default program for the tag"??
Example: Let's say that I am currently on tag 1 (called WEB) which default application would be qutebrowser (at least in my case) so I would like to hit MOD+D to spawn qutebrowser. But when I hit the same keybinding (MOD+D) on tag 2 i want awesome-wm to spawn Rstudio.
Note that I am not asking how to make a rule of spawning a certain program on specific tag (with a keybinding assigned to spawning this program) as there are a lot of answers to that around the web. I want to have conditional rule, based on which tag is active, to spawn a predefined app in this tag with one keybinding.
awful.keyboard.append_global_keybindings({
awful.key({ modkey }, "d", function()
local t = awful.screen.focused().selected_tag
if t.name == "WEB" then
-- launch qutebrowser
elseif t.name == "tag 2 name" then
-- launch Rstudio
end
end,
{description = "description", group = "group"})
})

Awesomewm update watch widget on keypress

I am a new user to awesomewm (but have used other WMs before: i3, bspwm, xmonad, etc). I like to have some shell scripts that I have written in my wibar (I think that's what its called, the bar at the top of the screen with the taglist) to display stuff like battery, audio, etc (as I know is common). Currently I am using the "wibar.widget.watch" to do this, as shown below.
-- Right widgets
layout = wibox.layout.fixed.horizontal,
awful.widget.watch('musicbar', 5),
wibox.widget.textbox(' | '),
awful.widget.watch('wifibar', 5),
wibox.widget.textbox(' | '),
awful.widget.watch('audiobar', 0.5),
wibox.widget.textbox(' | '),
awful.widget.watch('batbar', 5),
In the code above stuff like "audiobar" are scripts that return information as standard output. It all works perfectly, even displays the emojis well :). I have one problem (maybe just an optimization).
Currently I have the audiobar running twice a second, this is because that is the only one which directly changes based on my input (changing volume) and so I want it to change immediately (obviously this still has a <= 0.5 second delay, which is annoying). This means that most of the time it is updating twice a second unnecesarily.
So, I'm wondering if there is a way to have it update when I change the volume, which I've bound to the XF86 audio keys in rc.lua, instead of changing based on a timer. Based on my reading of the documentation, there is no way to do this with the watch widget, but as I said I am new to awesome.
Below is how I bound the keys (shouldn't make a difference, but I imagine that this is where the change would be made).
awful.key(
{},
"XF86AudioLowerVolume",
function()
awful.spawn("amixer -q set Master 5%-")
end,
{description = "lower volume", group = "control"}
),
I know that I can use some of the pre-made widgets on github to display volume and stuff but I like the shell scripts because they let me easily move between WMs and are simpler than those widgets (which I like because it means that I can more easily address problems with them and make them display exactly what I want, as well as learn).
edit: I am willing to learn to do this with lua, I just first want to see if I can easily do it with shell scripts.
You need to keep a reference to the timer around that awful.widget.watch creates internally. To do this, you need to do something like this in global context (i.e. outside of the definitions of the widgets or keybindings):
local musicbar_widget, musicbar_timer = awful.widget.watch('musicbar', 5)
You now add musicbar_widget to your wibox (instead of calling awful.widget.watch there). Now, you can "force-update" the widget via musicbar_timer:emit_signal("timeout"). This "pretends" to the widget that the timeout happened again.
In your keybinding (yes, I am mixing up your widgets here, most likely musicbar has nothing to do with the volume):
awful.key(
{},
"XF86AudioLowerVolume",
function()
awful.spawn("amixer -q set Master 5%-")
musicbar_timer:emit_signal("timeout")
end,
{description = "lower volume", group = "control"}
),
Note that this might or might not work. awful.spawn only starts the command, but does not wait for it to finish. So now you are changing the volume at the same time that your are querying it. If querying finishes faster than changing the volume, then the update does not actually occur.
To only update the widget after changing the volume is done, do something like the following:
awful.spawn.with_line_callback(
"amixer -q set Master 5%-", {
exit = function()
musicbar_timer:emit_signal("timeout")
end
})
I ran into a similar problem with streetturtle's volumearc widget. By default, it runs an update command:
Everytime the volume is modified through the widget.
Every second to catch any external volume changes (like with key bindings defined in rc.lua).
watch(get_volume_cmd, 1, update_graphic, volumearc)
Of course this has the following disadvantages:
A slight delay in the volume update (one second in still very perceptible).
On my machine, a constant 1% CPU load just for this task. A trifle I know, but little things add up.
Using a returned update function
A possible solution is to return an update function, available in the context of rc.lua and call this update along the volume modifications.
In volumearc.lua, in the worker function, we put the widget update into a dedicated function:
local ext_update = function()
spawn.easy_async(get_volume_cmd,
function(stdout, stderr, exitreason, exitcode)
update_graphic(volumearc, stdout, stderr, exitreason, exitcode) end)
end
And we return it both from the worker function:
return volumearc, ext_update
and from the widget itself:
volumearc, ext_update = { __call = function(_, ...) return worker(...) end }
return setmetatable(widget, volumearc), ext_update
Now we can use it in rc.lua:
local volumearc_widget = require("widgets.volume-widget.volumearc")
-- ...
local GET_VOLUME = "amixer sget Master"
local INC_VOLUME = "amixer sset Master 3%+"
local DEC_VOLUME = "amixer sset Master 3%-"
local TOG_VOLUME = "amixer sset Master toggle"
myvolume, volume_update = volumearc_widget({get_volume_cmd=GET_VOLUME,
inc_volume_cmd=INC_VOLUME,
dec_volume_cmd=DEC_VOLUME,
tog_volume_cmd=TOG_VOLUME})
-- `myvolume` is the widget that can be added as usual in the wibox.
-- `volume_update` is the function to call in order to trigger an update.
-- ...
-- In the key bindings:
awful.key({}, "XF86AudioMute",
function () awful.spawn(TOG_VOLUME) volume_update() end,
{description="mute", group = "media"}),
awful.key({}, "XF86AudioRaiseVolume",
function () awful.spawn(INC_VOLUME) volume_update() end,
{description="raise volume", group = "media"}),
awful.key({}, "XF86AudioLowerVolume",
function () awful.spawn(DEC_VOLUME) volume_update() end,
{description="lower volume", group="media"}),
Now the volume will be updated both when changed from the widget or from the media keys, instantly and without the need for polling.
Caveat
This does not catch changes made through other interfaces, such as alsamixer for example. To catch these changes, you might want to run the watch function with a very low time frequency (say once per minute).
The code given here is specific to streetturtle's widget, but the concept of returning an internal update function applies for any widget.

IBM Watson Assistant - Return after digression without responding again

I'm wondering if I can use a digression with a return to the previous flow, without having the bot responding again.
For the details, I'm using digressions so the user can jump from a child node to a root node (for example "wait"). The problem is that the conversation looks like this :
Hello, I'm Watson.
How are you ?
Fine and you ?
Wait a second... // Digression to node 'wait'
I'll hold on. // Digression back to previous node
Fine and you ? // I would like this not to be said again.
Any way of doing so without catching the "digression event" in back-end and manually delete the second "Fine and you ?" ?
Put <? clearDialogStack() ?> and digression will be cleared.

Client doesn't have a name/class at startup

I'm trying to start an application (Spotify) on a particular tag. Rules aren't applied and now I'm inspecting the client class by printing the class in a notification in the "manage" signal. This results in an empty notification.
client.connect_signal("manage", function (c, startup)
naughty.notify({title=c.class})
end)
When I restart awesome, it does print the client class, so why isn't it working when the client is initially started?
Using xprop, it also prints the class:
WM_CLASS(STRING) = "spotify", "Spotify"
Sounds like a bug in Spotify (and I think I heard about this one before). I would guess that Spotify does not follow ICCCM and only sets its WM_CLASS property after it made its window visible and not before.
I fear that you cannot do much about this except for complaining to Spotify devs to fix their stuff.
You could work around this by starting a timer in the manage signal that checks if a window turns out to be spotify a short time later. Alternatively, you could do something like client.connect_signal("property::class", function(c) if c.class == "Spotify" then print("This is now a spotify window") end end) to react to change to a window's class (of course you'd want to do something more useful to Spotify's windows than printing them).
(Per the ICCCM, a window is not allowed to change its class while it is visible, but who cares about standards...)
I had a similar issue with the claws-mail client. Inspecting it via xprop, it shows
WM_CLASS(STRING) = "claws-mail", "Claws-mail"
but awesome just did’t apply the rules for it. The trick was giving awesome-wm both of these class names in the rules section by providing a set of characters to chose from:
rule = {class = "[Cc]laws%-mail"}
I hope this works for your spotify application, too.
For further reading about patterns in lua I suggest this:
https://www.lua.org/pil/20.2.html

Selenium and wordpress: New post test

I've been looking a bit at Selenium, and I'm beginning to like it, since I know some Java programming and find both Java and C# pretty straight-forward for simple things like this.
However, I'm struggling with a test that creates a new post in Wordpress, from the Dashboard page:
This is the Selenium code (in C#):
(The Driver instance is, obviously, a driver class I've created - for starting the browser and connecting to the wordpress site.)
1: Driver.Instance.FindElement(By.Id("title)).SendKeys(title);
2: Thread.Sleep(1000);
3:
4: Instance.SwitchTo().Frame("content_ifr");
5: Thread.Sleep(1000);
6:
7: Driver.Instance.SwitchTo().ActiveElement().SendKeys("something");
Now, what happens is that the title is easily found (by ID, so I wouldn't expect problems there), and I can easily insert the title text (line 1).
But the inline frame for the post body is causing problems. When running the test, after the topic is filled in, the cursor changes to the body area (line 4) - as planned. However, nothing more happens. The SendKeys("string") method (ine 7) doesn't seem to work there.
Any ideas?
EDIT: Of course - an important piece of information is that the iframe in Wordpress simply loads a TinyMCE editor. So, in the page source, there's only a body tag with the javascript loading of the editor.
EDIT2: Of course, something suddenly changed. Without ANY change to the wordpress page, the "content_ifr" is now suddenly missing (?!!!!!?) The Selenium test fails with "unable to locate frame...", and it's also suddenly missing from the page source.
EDIT3: I also noticed something:
Driver.Instance.SwitchTo().Frame(iframe);
Driver.Instance.FindElement(By.Id("tinymce")).SendKeys("message body");
It's the SECOND line that makes the cursor switch to the mce field, not the line with the .SwitchTo(). However, I need the first line - the second line does nothing on its own. This is approaching something really stupid. I've been looking for a solution to this for a week - this doesn't exactly bode well for Selenium. The Selenium user group doesn't even want to answer when I ask them.
Also - if I skip the SendKeys() method in the second line, nothing happens. So, it seems that the two lines does ALLMOST what it should, right up to and including placing the cursor in the correct spot. But it never sends any text.
EDIT4 (last): After actually figuring out how to use IJavaScriptExecutor, it works using the solution(s) below.
Java method to handle TinyMCE editor would look like:
public void entersTopicOfBody(String textToBeTyped, WebDriver driver) {
driver.switchTo().frame("content_ifr");
WebElement body = driver.findElement(By.xpath("//body"));
body.click();
JavascriptExecutor executor = (JavascriptExecutor)driver;
executor.executeScript("arguments[0].innerHTML = '"+ textToBeTyped+"'", body);
driver.switchTo().defaultContent();
}
Below is some C# code that publishes a post. I think the main issues you have are due to timing issues.
I've done a bit of Selenium recently and I favour implicit waits: it waits for a maximum time period for the item to be available, but returns as soon as possible. So you can specify a max wait of 100 seconds, but if it finds it in 1 second, it will only wait 1 second. Much more efficient vs sleeping for an arbitrary length of time. See this post about Implicit and Explicit waits
But even with implicit waits, it may not solve all issues. When coding the sample below, I ran into the issue where the "Publish" button was disabled and re-enabled after some time. And that's when you have to look at the code to see what it is doing as well. It's times such as these where sleeps can help you fix the problem for a quick fix if you do not wish to debug too much: just be sure to set a large enough sleep time and be wary that it could be inconsistent in the future.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Support.UI;
using OpenQA.Selenium.Support.Events;
namespace SeleniumTest
{
class Program
{
static void Main(string[] args)
{
IWebDriver driver = new OpenQA.Selenium.Firefox.FirefoxDriver();
driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(30));
// enter your configurations here
driver.Navigate().GoToUrl("http://localhost/wordpress/wp-admin/post-new.php");
driver.FindElement(By.Id("user_login")).SendKeys("admin");
driver.FindElement(By.Id("user_pass")).SendKeys("yourpassword");
driver.FindElement(By.Id("wp-submit")).Click();
driver.FindElement(By.Id("title")).SendKeys("the title");
var iframe = driver.FindElement(By.Id("content_ifr"));
driver.SwitchTo().Frame(iframe);
// your solution which works in my instance
//driver.SwitchTo().ActiveElement().SendKeys("hello tiny mce from selenium");
// send keys with exact element
//driver.FindElement(By.Id("tinymce")).SendKeys("hello tiny mce from selenium");
// javascript - 1
IJavaScriptExecutor js = driver as IJavaScriptExecutor;
var tinymce = driver.FindElement(By.Id("tinymce"));
IJavaScriptExecutor executor = (IJavaScriptExecutor)driver;
executor.ExecuteScript("arguments[0].innerHTML = 'hello tiny mce via javascript'", tinymce);
// javascript - 0
driver.SwitchTo().DefaultContent();
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(11));
wait.Until((d) => { return !d.FindElement(By.Id("publish")).GetAttribute("class").Contains("disabled"); }); // wait for publish button to be enabled
driver.FindElement(By.Id("publish")).Click();
driver.FindElement(By.Id("message")); // wait for message on next page to verify it is posted
driver.Close();
}
}
}
A PHP version of olyv solution:
$content = 'my text';
$this->frame( 'content_ifr' );
$body = $this->byXPath( '//body' );
$body->click();
$script = 'arguments[0].innerHTML = "" + arguments[1] + ""; ';
$this->execute( [
'script' => $script,
'args' => [ $body->toWebDriverObject(), $content ],
]
);
$this->frame( null );
I know that I am a bit late to the party, but I just found a solution that is (I believe) much simpler than the answers given so far. So I decided to post it here in case it could help someone else.
There is no need to switch frames here. What you wanna do is 'click' on the button in the top right corner of the text editor that says "Text", which has id = "content-html". Now, you can 'send keys" to the textarea, which has id = "content".
Here is some Python code that does just this:
driver.find_element_by_id("content-html").click()
driver.find_element_by_id("content").send_keys("Some text...")
Hope it helps

Resources