-
Notifications
You must be signed in to change notification settings - Fork 587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
User Facing Screenshot API #3461
Conversation
Hello again everyone. I wanted to post this so the project maintainers could get eyes on it and give feedback. As noted in the commit message, I did not modify rc.lua. Please see a sample binding below. At this time, I did not add test cases because it seems they cause race conditions and I am expecting churn after people examine this. I will add new test cases to tests/test-screenshot.lua before the PR is done.
Looking forward to hearing from you all. |
Codecov Report
@@ Coverage Diff @@
## master #3461 +/- ##
==========================================
+ Coverage 90.93% 91.00% +0.07%
==========================================
Files 897 900 +3
Lines 56682 57549 +867
==========================================
+ Hits 51542 52372 +830
- Misses 5140 5177 +37
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Hello, thanks for this. Lets get started with some of the mundane "things to learn when contributing to AwesomeWM". That's pretty much "normal" for when contributing to projects. Your previous contribution was pretty low level, so needed less of that. None of what's below is a criticism, it's just explaining many undocumented conventions and API design guidelines we use for high level APIs. This is the first pass. Once this settles down, there will be some testing and code examples work to do, but that's for another day. First, there is a tool called
Next, the documentation needs 3 As you can see, there is still a couple trivial fixes. The format for the parameter is So that's for the trivial "how to get the CI to pass and the doc to render" things. Now, let's move into the code a little bit. awful.screenshot.initOur objects constructors are (generally) directly the module name. In Lua, you do that using meta tables. Here's how: local gobject = require("gears.object")
local gtable = require("gears.table")
local module = {}
--- A property!.
-- @property my_property
-- @tparam[opt=default] string my_property
function module:get_my_property()
-- You don't need this accessor of the default is
-- self._privaye.property_name
return self._private.my_property
end
function module:set_my_property(value)
-- This accessor can contain business logic and have side effects
self._private.my_property = value
self:emit_signal("property::value", value)
end
--- This is a method.
-- @method my_method
function module:my_method()
-- do something
end
local function new(_, args)
args = args or {}
local ret = gobject {
enable_auto_signals = true,
enable_properties = true,
}
ret._private = {
my_property = "default_value"
}
-- This adds all the method and properties
gtable.crush(ret, module, true)
-- This apply all the constructor arguments.
gtable.crush(ret, args, false)
return ret
end
return setmetatable(module, { __call = new })
So, The API in general.For the API, right now, you have some static functions. I would like to see this moved to a more declarative/OOP style API. This means using a main objects with some properties and method. With static function, it's hard to scale the API on the long term. For example, creating some composition like "take a screenshot of object foo, scale it, crop it, save it". With a more object oriented API, this comes pretty naturally. It is also easier to extend with new features, since you don't have to add endless parameters to each static functions. For example, the API could look like: local ss = awful.screenshot {
client = client.focus, -- automatically use client.content, by default, use root.content
rectangle = {
x = 0,
y = 0,
width = 100,
height = 100
}
}
ss:save("/tmp/image.png")
-- Because why not!
mywibar.widget.some_layout.add(ss.widget)
It also has the advantages of being a memento. Right now, |
Great, thanks for this. I will start to implement the documentation changes and I can probably get what I have into the OO style. The problem with the tparams seems to be that I did 'tparam[opt] string The...' so I actually did put the type. Is the the 'something' in 'opt=something' the default value? |
Hey, before I go too crazy, can we get people to give some input on what an "object oriented" library should look like? My initial submission centered around taking the various kinds of screenshots, saving them to files, and returning the path to the newly saved file. The various kinds included root window, physical screen, client window, interactive snip tool, and programmatically defined crop. The directory and base filename of the saved screenshot could be specified, but essentially defaulted to something that would likely work. Otherwise, it could be "initialized" once and called over and over again with no further arguments. I can change this to an "object oriented" design, but is there anything else people think makes sense? EDIT: It is worth noting that instead of returning just a string with the path, an object containing the surface itself and some manipulation methods can be returned. It does not need to save automatically, either, but instead require a ss:save_to_file("/path/to/file.png") as you say. I figured that this is so common, though, that it would become "boilerplate" code, and so should be in the library itself. This is especially the case with appending a date_time string to the file base name to make it unique (and easily searchable if you know about when you took a particular shot). |
339c20b
to
35fd988
Compare
Hi everyone, sorry to have dropped off on this one. I have been busy with some other things. I corrected the documentation generation syntax. The issue was that the correct format seems to be Please provide a little direction as to what the OO system should look like. I still think there is a disconnect between thinking of "screenshot" as the library for taking the screenshots (how I am using the term) or as an actual image. The way I am looking at it, we need some interface for the user to request a screenshot be taken and how (the root window, the client, a particular screen, a snip tool, a defined crop of the screen). If the desire is for that action to create a new object (that can be saved, scaled, put in a widget, whatever), then I understand that. However, for the screenshot library itself to return objects is a little weird to me (it can certainly be done that way). For example, beautiful uses static methods like beautiful.init(), and gears holds all sorts of static methods like gears.wallpaper.fit(). I personally think it is natural for there to be a similar screenshot library that can be called upon to perform actions, such as awful.screenshot.root() to take a full screenshot or awful.screenshot.client() to get the active client content, but I am open to a different direction. It is worth remembering that if we don't do that, there still needs to be a way to distinguish between the different types of screenshots. So root/screen/client will need to be an argument passed to screenshot.new(), or something along those lines. I find that to be less natural. If you are looking for the library itself to be an object, please discuss with me the class layout you are visualizing. Otherwise, if the desire is for the screenshot methods to return objects with properties and methods, such as Input from some of the others on here welcome, @actionless @Aire-One @psychon |
i think the next step would be to improve the coverage level to pass the CI check:
|
I'm not sure how that part works. |
i think you can start from the existing tests for low-level screenshot api as a starting point |
or mb adding |
I think the disconnect might come from the fact that, while pure static libraries like In this case, I agree with Elv that a more OOP-like API would be the better fit long-term. I.e. But that doesn't mean that it can't also cover the use cases you describe.
awful.screenshot.set_default_path(os.getenv("HOME") .. "/Pictures/Screenshots")
awful.screenshot.set_name_template("%Y-%m-%d.png") -- see GLib.DateTime.format
local shot = awful.screenshot()
shot:save() -- Build file path from defaults
shot:save("/tmp/just_a_test") -- Override defaults. If `just_a_test` is a directory, use name template to store inside
local shot = awful.screenshot()
local small_shot = shot:scale(0.5)
local fancy_shot = shot
:draw_rectangle(...)
:draw_circle(...)
:text(...) If you combine the first two, your use case above becomes -- Once at the start
awful.screenshot.set_default_path(os.getenv("HOME") .. "/Pictures/Screenshots")
awful.screenshot.set_name_template("%Y-%m-%d.png")
-- In a hotkey
awful.screenshot.client():scale(0.5):save() It's a few more characters to write in the hotkey hander, but more expressive, without hidden defaults/constants and allows for a much more powerful and extensible API, as Elv already described. |
@actionless sorry I misunderstood. I didn't realize that the CI metric was what the tests covered. Can I fix that by just adding tests that use the new methods in the tests/*.lua files? @sclu1034 I agree with your line of thinking and would point out that you are naturally thinking of the object library itself, awful.screenshot, as something distinct from the returned screenshot, which you are calling `shot'. This is what I noticed too, we really have two (albeit related) object hierarchies here. |
@poisson-aerohead yup, either tests or examples - both counted for coverage |
Hi everyone. Sorry for the long hiatus on this one, but as you know, I did have the awful.screenshot library working as I wanted on my own machine, and trying to rewrite this in the style you requested took a bit of a backseat. Some quick notes. As we discuss on this thread, the awful.screenshot library distinguishes between the library itself and the returned screenshot object. So there is a generic constructor, The returned object has methods like ss:save(), that can take optional arguments. The module should work out of the box with the following rc.lua:
You can optionally configure it first with
if desired. I'm sure there will be some issues with getting the tests to pass, which I am a bit underinformed on. Also, the documentation could use a bit of expansion. but it should at least have a basic description for everything. |
Hey everyone. I don't know what's going on with the failure. On my machine, I can just call it with no configuration. What's weird is that the default prefix is hard coded to "Screenshot-" so I can't imagine any way you could end up with it being nil. EDIT: |
idk, i was trying to look into your code but you have single-letter var names so i gave up, but good luck fixing it :) |
@actionless most of my var names are pretty descriptive. I think there might be a few temporary variables where I use that. I got all the tests to successfully run. I currently am doing this by hard coding a path in my test script like EDIT:
|
OK, problem solved, well, at least identified. It seems that $HOME in the Xephyr environment is '/dev/null/'. I guess I can understand why this would be the case. I don't know how to fix it to run on the general machine, so I need some input from you all. Here is the thing, my initialization code is going to try set the 'directory' property to something writable. I suppose I can try to change the initialization logic, but it seems like a good idea to do it the way I did and not weaken the class module to accommodate the test suite. So, what is a good, robust way for the test script to find a writable directory? It does not actually need to write there (the test suite is currently not saving any files). But the init logic will check that the directory is writable. |
lib/awful/screenshot.lua
Outdated
-- unitialized state and without passing any arguments. | ||
local function configure_path(directory, prefix) | ||
|
||
local d, p |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i was talking of these specifically
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll make them dir, prfx
or something.
lib/awful/screenshot.lua
Outdated
|
||
-- Configuration data | ||
local ss_dir = nil | ||
local ss_prfx = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but those would be nice also to make more maintainable
if you have troubles typing long names - you can just press Ctrl+xn in vim to auto-complete it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah its OK, I don't mind. If you look, most of the names are descriptive. It is usually just temporaries in helper functions where I don't do that, but I'll clean them up for the next push.
lib/awful/screenshot.lua
Outdated
|
||
dir, prfx = configure_path(args.directory, args.prefix) | ||
|
||
local fclr = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
frame_color
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor I intentionally did this with everything to avoid name collision. I thought it would be easier to follow. Maybe the constructor local variables should all be named as '_frame_color' ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or mb try using russian 'а' and just name them the same? ;)
lib/awful/screenshot.lua
Outdated
-- Configuration data | ||
local ss_dir = nil | ||
local ss_prfx = nil | ||
local frame_color = gears.color("#000000") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
module.frame_color = ....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have updated these three tentatively (this is how they function if no argument provides a value)
-- Configuration data
local global_screenshot_dir = nil
local global_screenshot_prefix = nil
local global_frame_color = gears.color("#000000")
local initialized = nil
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using module properties would be better, but now it looks at least readable 👍
leave short var names for either logically reasonable (like x, y coordinates, etc) or ones conventional for awesome API (c, s, cr, etc) |
lib/awful/screenshot.lua
Outdated
|
||
-- If we use single quotes, we only need to deal with single quotes - (I | ||
-- promise that's meaningful if you think about it from a bash perspective) | ||
local dir = string.gsub(directory, "'", "'\\'\\\\\\'\\''") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what the difference between dir and directory??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that one is actually for a reason but I can do a better name. That is the escaped string to be passed through bash. You'll see that if dir is writable, that function returns directory. That is why there are two.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that should be clear from var name - not from your comment
it makes no sense giving basically the same name to 2 variables which have different logical meaning
also please double-check what none of your helper functions is already implemented in |
lib/awful/screenshot.lua
Outdated
-- @tparam string prefix The prefix prepended to screenshot files names. | ||
function screenshot:set_prefix(prefix) | ||
if type(prefix) == "string" then | ||
local prfx = check_prefix(prefix) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and again, prfx = prefix
- that's very confusing to understand without unwinding every code branch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is just avoiding a collision between the argument name and the local variable which will be stored as a property if it is valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try to name the var more clear according to their logical roles in the code, not just randomly duplicate the same name and "avoid a collision"
tests/run.sh
16:export HOME=/dev/null
199: XDG_CONFIG_HOME="$build_dir" |
lib/awful/screenshot.lua
Outdated
local d = os.getenv("HOME") | ||
|
||
if d then | ||
d = string.gsub(d, '/*$', '/') .. 'Images/' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
read XDG spec about images' dir
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found this on Archlinux on the user directories. How do you want this handled for the screenshot module? It looks like it would be in '~/.config/user-dirs.dirs'. I say in the comment we can expand it to include parsing such a file. Is there already a gears routine to get this?
EDIT: This too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see our gears.filesystem please for similar functions (or mb even better add them there, not here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do you trying to find XDG spec on arch linux website? here is not only explained everything, but even given example (very similar to our gears, again):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i believe the most practical way would be to parse that file and replace $HOME to currently detected home dir
this is still not very robust - but would avoid making code asynchronous for external call of bash
There will still be one more commit to replace some of the screenshot module code with available functionality from gears (e.g. the filesystem module).
request from awesome maintainers.
to increase test coverage.
* Fix all warnings * Make indentation consistent across the file * Simplify/unify the validation (use `error()` rather than try to silently fix problems, move to setters) and fallback code (move to the getters rather than... everywhere) * Write the documentation * Finish the tests * Remove unnecessary constructors * Remove path builder because it belongs in `gears.filesystem` and wasn't really used anyway * Add more properties and a beautiful variable to replace `set_defaults` and hardcoded values. * Replace callbacks with signals (common pattern in modern AwesomeWM APIs) * Moved from `os.date` to GLib and some boilerplate code to make Debian devs less angry * Changed the way the snipping tool selection wibox works. Now it freeze the screenshot before the selection. The old way had a bunch of side effects for things like Qt/GTK comboboxes. It also could crash.
Some downstream modules with keys don't control the callbacks. Using signals on multiple keys is more cumbersome than simply exposing this at the keygrabber level.
It was possible to add new keys, but not remove existing ones.
It was called `mod` rather than `modifiers`.
This can also act as an auto-save feature if the delay is zero. It also adds more signals. These signals are intended for creating notifications. `awful` cannot safely depend on `naughty`, so this intergration will have to be done by the users.
Useful for alt-tab widgets and CDE / E16 style Iconified clients.
Needed for the screenshot API.
47de5c9
to
e088fe9
Compare
This commit begins the development of a more appropriate user facing
screenshot API. It extends a prior commit which extended the lower level
content API, which had been a property of the client object but is now
available as a property of the screen object and a method of the root
object.
This commit creates a new screenshot module for the awful module. The
public functions include root(), screen(), client(), snipper(), and
snip(). These take root window, screen, and client window screenshots,
launch an interactive snip tool for cropped screenshots, and take a
cropped screenshot of a geometry passed by argument, respectively. The
init() function is also available for configuration. Using this library
is more appropriate for the average rc.lua.
Since the API is new, this commit does not include any changes to
rc.lua. The developers can modify rc.lua when there is sufficient
confidence in API stability and robustness.
lib/awful/init.lua is modified so that the awful module includes the new
lib/awful/screenshot.lua submodule.
Signed off: Brian Sobulefsky brian.sobulefsky@protonmail.com