Skip to content
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

Mixed-DPI groundwork #2053

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open

Conversation

Oblomov
Copy link
Contributor

@Oblomov Oblomov commented Oct 7, 2017

Some more groundwork to improve support for mixed-DPI setups.

It pre-computes the per-screen DPI, while still allowing overrides, it allows choosing a rounding method for the used DPI, and font loading now allows an associated screen, to be used for height computation.

(Some of these make more sense view in terms of the subsequent fixes spread around the lib part, the work is pushed to my mixed-dpi branch, but still needs a more thorough review.)

@codecov
Copy link

codecov bot commented Oct 7, 2017

Codecov Report

Merging #2053 into master will increase coverage by 0.12%.
The diff coverage is 87.5%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2053      +/-   ##
==========================================
+ Coverage   79.55%   79.67%   +0.12%     
==========================================
  Files         404      405       +1     
  Lines       27652    27713      +61     
  Branches      991      991              
==========================================
+ Hits        21998    22081      +83     
+ Misses       5218     5198      -20     
+ Partials      436      434       -2
Flag Coverage Δ
#c_code 65.67% <ø> (+0.21%) ⬆️
#functionaltests 66.11% <87.5%> (+0.19%) ⬆️
#samples 67.36% <34.54%> (-0.06%) ⬇️
#unittests 65.65% <49.01%> (-0.11%) ⬇️
Impacted Files Coverage Δ
objects/screen.c 53.15% <ø> (+0.87%) ⬆️
lib/gears/dpi.lua 100% <100%> (ø)
lib/awful/hotkeys_popup/widget.lua 91.61% <100%> (+0.07%) ⬆️
lib/awful/wibar.lua 80.4% <100%> (ø) ⬆️
lib/gears/init.lua 100% <100%> (ø) ⬆️
lib/awful/mouse/snap.lua 81.16% <100%> (ø) ⬆️
lib/awful/screen.lua 79.23% <100%> (+4.23%) ⬆️
lib/wibox/widget/textbox.lua 94.24% <100%> (+1.33%) ⬆️
lib/beautiful/xresources.lua 69.64% <20%> (-9.39%) ⬇️
lib/naughty/core.lua 68.54% <75%> (-0.28%) ⬇️
... and 11 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5ac6db5...b823218. Read the comment docs.

@Oblomov Oblomov force-pushed the mixed-dpi-groundwork branch 7 times, most recently from 1e7a7a8 to b6210ca Compare October 8, 2017 06:25
if #fake_screen == 0 then
return true
end
return #fake_screen == 0
Copy link
Member

@psychon psychon Oct 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test runner (tests/_runner.lua) does the following after a step ran:

  • If it returned true, it continues with the next step
  • If it returns false, the whole test is considered to have failed
  • If it returns nil, this step is retried after 0.1 seconds, but only up to 5 times. Then the test is considered to have failed.

So, before this commit, the garbage collector had up to 5 calls to collectgarbage("collect") time to get rid of the old screen. Now the screen must be removed on the first collection cycle.

I guess this change is the reason why the Travis build failed. Also, this cannot stall, since the test runner only tries up to five times and then gives up.

Please remove this commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Travis build was failing even before I introduced this commit, due to (I think) references to screen being held in the new font cache. But thanks for the explanation, I'll remove the commit.

if dpi_per_screen[s] then
return dpi_per_screen[s]
if s then
return dpi_per_screen[s] or s.dpi
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I query the DPI of some screen (which has no outputs), then the result will always be 96, since this is what the get_dpi function above returns. Shouldn't this rather use some of the calculations below instead if no outputs are available?
So, my proposal:

if dpi_per_screen[s] then
    return dpi_per_screen[s]
end
if #s.outputs > 0 then
    return s.dpi
end

if dpi_per_screen[s] then
return dpi_per_screen[s]
if s then
return dpi_per_screen[s] or s.dpi
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the next problem: People who have wrong dimensions in their EDID data and overrode this via Xft.dpi will now have Xft.dpi being ignored. Since Xft.dpi is some explicit configuration, I think it should have higher priority than auto-configuration.

Meaning that I would propose the following order of settings:

  • Forced settings via Lua: dpi_per_screen[s]
  • Forced settings via xrdb: Xft.dpi
  • Automatically computed from outputs: s.outputs
  • Automatically computed from the size of the root window (the code below)
  • Good old 96

-- 96 DPI, according to the function used
-- @tparam function func a function to be applied to the DPI scaling factor
function xresources.set_dpi_rounding(func)
dpi_scale_rounding = func
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but this function is useless. It gets as argument the value dpi/96. If you want to e.g. ceil this, you could just do set_dpi(s, 96*math.ceil(get_dpi(s)/96)) in your rc.lua (or just compute the wanted DPI value by hand). So, this does not add any new possibilities.

Put differently: Isn't it enough to be able to override DPI per-screen?

If you want a rounding function, I would more expect the user to override the round call in apply_dpi. However, that function must return integers, so this might not be all that useful either.

@@ -145,6 +145,7 @@ end
-- @tparam[opt] integer|screen s The screen.
-- @treturn integer Resulting size (rounded to integer).
function xresources.apply_dpi(size, s)
if not size then return size end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? Which code would pass a nil (or false) here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in my mixed-dpi branch I remove a number of uses of the global apply_dpi, shifting the computation of the dpi scaling at use time rather than define time. There are a few cases in which the value is optional with a number of fallbacks that can all be nil (local somevar = args.stuff or default.stuff or blah.stuff). Rather than leaving that and then adding a new somevar = somevar and dpi(somevar, s) or nil line I decided to just wrap everything in a dpi(..., s), which then needs to pass through nil values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, but I guess then this behaviour should be added to the documentation of this function.

@@ -64,6 +64,9 @@ function xresources.get_current_theme()
end


--- Store the cached or user-set per-screen DPI
-- Values are tables with key dpi (the dpi) and set_from (user, auto, xrdb)
-- The same is done for xresources.dpi
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What this commit actually does is to add a cache to DPI values. Why? Isn't the existing state of recomputing this "all the time" good enough? Do you have some example where adding a cache here speeds something up significantly?

return fonts[name]
local fonts_key = {name, screen}
if fonts[fonts_key] then
return fonts[fonts_key]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. I totally forgot about this function ignoring DPI.

Anyway, the above cache check does not work. Lua tables are only equal to themselves, i.e. the index operator does not do a deep comparison:

$ lua -e 't={} t[{}] = 42 print(t[{}])'
nil

Thus, the above doesn't cache check does not work.

Also, I wonder if it makes sense to have a version of this function that gets DPI as argument and another that gets a screen object, gets the screen's DPI and then just calls the other function (same for get_font_height). Thoughts?

(At least the cache only needs the DPI, not the screen itself)

@Oblomov Oblomov force-pushed the mixed-dpi-groundwork branch 7 times, most recently from ced6bdf to 1994a6f Compare October 17, 2017 21:31
@Oblomov Oblomov force-pushed the mixed-dpi-groundwork branch 2 times, most recently from bbfbab8 to 6c8ce09 Compare October 22, 2017 15:49
Otherwise we don't get the events that mark a runtime DPI update.
When picking the font height, offer the possibility to compute it based
on the DPI of a specific screen, to improve support for mixed-DPI
environments.
@Oblomov Oblomov force-pushed the mixed-dpi-groundwork branch 2 times, most recently from 4b41b2b to 1f9e1ff Compare October 22, 2017 22:17
In the process of deprecation of the old xresources.*dpi API,
the global font DPI management is moved to beautiful itself.

(awful.screen was also a good candidate, but this led to some pretty
messy nearly irresolvable interdependencies between awful and
beautiful.)
Allow consistent font scaling in mixed-DPI environments.
The per-screen DPI does not make use of Xft.dpi now, and if RANDR
information is not available we fall back to the core DPI.
Scale a length to account for the DPI of the given screen.
Not all user appreciated fractional scaling, make it easier for them to
snap to integers.
Values should be defined unscaled, and scaled based on the DPI of the
screen where the popup appears.
@Oblomov
Copy link
Contributor Author

Oblomov commented Oct 28, 2017

Ping @psychon @blueyed @Elv13 review?

Copy link
Member

@psychon psychon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno if I really like this or not, but here are some comments. What do others think? @Elv13 @blueyed @actionless

end
end
return dpi
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good idea to have? If we want to have per screen DPIs, there should be no "global" DPI that could accidentally be used in code instead.

@@ -98,6 +100,41 @@ local active_font
--- The current theme path (if any)
-- @tfield string beautiful.theme_path

--- Font DPI API
-- @section font_dpi
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a good idea why for fonts the number of pixels per inch of your monitor is different than for non-fonts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I fail to parse my own sentence: The docs here could explain the concept for "fonts get a different DPI" a bit here.

function beautiful.screen_font_dpi(scr)
scr = scr and screen[scr] or screen.primary
return beautiful.get_font_dpi()*scr.dpi/screen.primary.dpi
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Elv13 What do you think of this?

@@ -255,7 +256,7 @@ end
-- @tparam[opt] screen scr Screen to get the DPI information from.
-- If unspecified, the global font DPI setting will be used.
function beautiful.get_font_height(name, scr)
return load_font(name, beautiful.xresources.get_dpi(scr)).height
return load_font(name, beautiful.screen_font_dpi(scr)).height
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this opens quite a can of worms.
Primary screen changed? Font height changed on all screens!
Primary screen changed DPI value? Font height changed on all screens!
Some random screen changed DPI value? Font height changed for that screen.

I bet that nothing will handle these correctly.

-- So we offer the possibility for the user to choose a global policy,
-- and all DPI usage will take this into consideration.
-- Note that rounding is only applied to autocomputed DPI values, never
-- when the user sets the DPI themselves.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't say I'm convinced that this is a good idea, but at least the default is identity.
@Elv13 What do you think?


runner.run_steps(steps)

-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay for tests! :-)

I think this should be done as an unit test instead of integration (i.e. as spec/gears/dpi_spec.lua). gears.dpi does not use much APIs, so that can easily be mocked (it just needs root, I think).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to ping me if needed.

end
local dpi = beautiful.screen_font_dpi(s)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the semantics of which DPI value is used when this function is called with s=nil. Previously, it used some concept of "core DPI" while now it uses the DPI of the primary screen.
@Elv13 Does that count as a regression?

if s then
return s:dpi_scale(size)
else
return round(size * require("beautiful").screen_font_dpi()/96)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this use font DPI? If you just use the font DPI everywhere, then s.dpi is not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an issue about “what has this call being used for in legacy code” and “how do we transition from there to the API we want now”.

Among other things, this means that the only case where you access the DPI without a screen specification is if you want to know the user-specified font scaling, since there is (or should not be) any concept of “global DPI” (otherwise).

if s then
dpi = screen[s].dpi
else
if not s then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is with :fit and :draw? This commits make the "non-widget" functions use a different DPI value than the actual drawing does.

Copy link
Member

@psychon psychon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocked by #2091, we should first decide about the route before we go hiking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants