Merge pull request #2886 from Elv13/screen_init_merge_part1

(ignore this) Part 1 of the Lua managed screen PR
This commit is contained in:
Emmanuel Lepage Vallée 2019-09-29 18:54:38 -04:00 committed by GitHub
commit 14c78ef19c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 878 additions and 139 deletions

View File

@ -50,14 +50,14 @@ addons:
jobs:
include:
- env: LUA=5.2 LUANAME=lua5.2 DO_COVERAGE=coveralls
- env: LUA=5.2 LUANAME=lua5.2 DO_COVERAGE=coveralls MANUAL_SCREENS=1
addons:
apt:
packages:
- *BASE_PACKAGES
- liblua5.2-dev
- lua5.2
- env: LUA=5.3 LUANAME=lua5.3 DO_COVERAGE=codecov
- env: LUA=5.3 LUANAME=lua5.3 DO_COVERAGE=codecov MANUAL_SCREENS=1
addons:
apt:
packages:
@ -229,7 +229,8 @@ install:
return 0
}
script:
- export CMAKE_ARGS="-DLUA_LIBRARY=${LUALIBRARY} -DLUA_INCLUDE_DIR=${LUAINCLUDE} -D OVERRIDE_VERSION=$AWESOME_VERSION -DSTRICT_TESTS=true -D DO_COVERAGE=$DO_COVERAGE -D CMAKE_C_FLAGS=-Werror"
- if [ "$MANUAL_SCREENS" != "1" ]; then export MANUAL_SCREENS=0; fi
- export CMAKE_ARGS="-DLUA_LIBRARY=${LUALIBRARY} -D LUA_INCLUDE_DIR=${LUAINCLUDE} -D OVERRIDE_VERSION=$AWESOME_VERSION -D STRICT_TESTS=true -D DO_COVERAGE=$DO_COVERAGE -D TEST_MANUAL_SCREENS=$MANUAL_SCREENS -D CMAKE_C_FLAGS=-Werror"
- |
if [ "$EMPTY_THEME_WHILE_LOADING" = 1 ]; then
# Break beautiful so that trying to access the theme before beautiful.init() causes an error

View File

@ -404,6 +404,12 @@ target_link_libraries(test-gravity
if(DO_COVERAGE)
set(TESTS_RUN_ENV DO_COVERAGE=1)
endif()
# Start AwesomeWM tests with `--screen off`
if ("${TEST_MANUAL_SCREENS}" MATCHES "1")
set(TEST_RUN_ARGS "--W --m")
endif()
add_custom_target(check-integration
${CMAKE_COMMAND} -E env CMAKE_BINARY_DIR='${CMAKE_BINARY_DIR}' ${TESTS_RUN_ENV} ./tests/run.sh \$\${TEST_RUN_ARGS:--W}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}

View File

@ -131,6 +131,8 @@ awesome_atexit(bool restart)
/* Close Lua */
lua_close(L);
screen_cleanup();
/* X11 is a great protocol. There is a save-set so that reparenting WMs
* don't kill clients when they shut down. However, when a focused windows
* is saved, the focus will move to its parent with revert-to none.
@ -559,13 +561,14 @@ exit_help(int exit_code)
FILE *outfile = (exit_code == EXIT_SUCCESS) ? stdout : stderr;
fprintf(outfile,
"Usage: awesome [OPTION]\n\
-h, --help show help\n\
-v, --version show version\n\
-c, --config FILE configuration file to use\n\
--search DIR add a directory to the library search path\n\
-k, --check check configuration file syntax\n\
-a, --no-argb disable client transparency support\n\
-r, --replace replace an existing window manager\n");
-h, --help show help\n\
-v, --version show version\n\
-c, --config FILE configuration file to use\n\
--search DIR add a directory to the library search path\n\
-k, --check check configuration file syntax\n\
-a, --no-argb disable client transparency support\n\
-m, --screen on|off enable or disable automatic screen creation (default: on)\n\
-r, --replace replace an existing window manager\n");
exit(exit_code);
}
@ -594,6 +597,7 @@ main(int argc, char **argv)
{ "search", 1, NULL, 's' },
{ "no-argb", 0, NULL, 'a' },
{ "replace", 0, NULL, 'r' },
{ "screen" , 1, NULL, 'm' },
{ "reap", 1, NULL, '\1' },
{ NULL, 0, NULL, 0 }
};
@ -634,6 +638,14 @@ main(int argc, char **argv)
if (confpath != NULL)
fatal("--config may only be specified once");
confpath = a_strdup(optarg);
break;
case 'm':
/* Validation */
if ((!optarg) || !(A_STREQ(optarg, "off") || A_STREQ(optarg, "on")))
fatal("The possible values of -m/--screen are \"on\" or \"off\"");
globalconf.no_auto_screen = A_STREQ(optarg, "off");
break;
case 's':
string_array_append(&searchpath, a_strdup(optarg));
@ -901,20 +913,44 @@ main(int argc, char **argv)
ewmh_init_lua();
/* Parse and run configuration file before adding the screens */
if (globalconf.no_auto_screen)
{
/* Disable automatic screen creation, awful.screen has a fallback */
globalconf.ignore_screens = true;
if(!luaA_parserc(&xdg, confpath))
fatal("couldn't find any rc file");
}
/* init screens information */
screen_scan();
/* Parse and run configuration file */
if (!luaA_parserc(&xdg, confpath))
/* Parse and run configuration file after adding the screens */
if (((!globalconf.no_auto_screen) && !luaA_parserc(&xdg, confpath)))
fatal("couldn't find any rc file");
p_delete(&confpath);
xdgWipeHandle(&xdg);
/* Both screen scanning mode have this signal, it cannot be in screen_scan
since the automatic screen generation don't have executed rc.lua yet. */
screen_emit_scanned();
/* Exit if the user doesn't read the instructions properly */
if (globalconf.no_auto_screen && !globalconf.screens.len)
fatal("When -m/--screen is set to \"off\", you **must** create a "
"screen object before or inside the screen \"scanned\" "
" signal. Using AwesomeWM with no screen is **not supported**.");
client_emit_scanning();
/* scan existing windows */
scan(tree_c);
client_emit_scanned();
luaA_emit_startup();
/* Setup the main context */

View File

@ -137,6 +137,7 @@ file = {
'../lib/awful/dbus.lua',
'../lib/awful/init.lua',
'../lib/awful/remote.lua',
'../lib/awful/screen/dpi.lua',
'../lib/awful/startup_notification.lua',
'../lib/awful/mouse/drag_to_tag.lua',
'../lib/gears/init.lua',

View File

@ -55,6 +55,7 @@
}
typedef struct drawable_t drawable_t;
typedef struct a_screen_area screen_area_t;
typedef struct drawin_t drawin_t;
typedef struct a_screen screen_t;
typedef struct button_t button_t;
@ -111,6 +112,10 @@ typedef struct
bool have_randr_15;
/** Do we have a RandR screen update pending? */
bool screen_refresh_pending;
/** Should screens be created before rc.lua is loaded? */
bool no_auto_screen;
/** Should the screen be created automatically? */
bool ignore_screens;
/** Check for XTest extension */
bool have_xtest;
/** Check for SHAPE extension */

View File

@ -21,7 +21,7 @@ end
--
-- @param obj An object that should have a .screen property.
local function check_focus(obj)
if not obj.screen.valid then return end
if (not obj.screen) or not obj.screen.valid then return end
-- When no visible client has the focus...
if not client.focus or not client.focus:isvisible() then
local c = aclient.focus.history.get(screen[obj.screen], 0, filter_sticky)

View File

@ -99,6 +99,9 @@ local delayed_arrange = {}
-- @staticfct awful.layout.get
function layout.get(screen)
screen = screen or capi.mouse.screen
if not screen then return nil end
local t = get_screen(screen).selected_tag
return tag.getproperty(t, "layout") or layout.suit.floating
end
@ -308,7 +311,7 @@ capi.screen.connect_signal("padding", layout.arrange)
capi.client.connect_signal("focus", function(c)
local screen = c.screen
if layout.get(screen).need_focus_update then
if screen and layout.get(screen).need_focus_update then
layout.arrange(screen)
end
end)

View File

@ -679,6 +679,30 @@ capi.screen.connect_signal("request::wallpaper::connected", function(new_handler
end
end)
-- Create some screens when none exist. This can happen when AwesomeWM is
-- started with `--screen off` and no handler is used.
capi.screen.connect_signal("scanned", function()
if capi.screen.count() == 0 then
-- Private API to scan for screens now.
if #capi.screen._viewports() == 0 then
capi.screen._scan_quiet()
end
local viewports = capi.screen._viewports()
if #viewports > 0 then
for _, area in ipairs(viewports) do
local geo = area.geometry
capi.screen.fake_add(geo.x, geo.y, geo.width, geo.height)
end
else
capi.screen.fake_add(0, 0, 640, 480)
end
assert(capi.screen.count() > 0, "Creating screens failed")
end
end)
--- When the tag history changed.
-- @signal tag::history::update

0
lib/awful/screen/dpi.lua Normal file
View File

View File

@ -258,6 +258,88 @@ function gtable.merge(t, set)
return t
end
--- Update the `target` table with entries from the `new` table.
--
-- Compared to `gears.table.merge`, this version is intended to work using both
-- an `identifier` function and a `merger` function. This works only for
-- indexed tables.
--
-- The main use case is when changing the table reference is not possible or
-- when the `target` contains additional content that must be kept.
--
-- Note that calling this function involve a lot of looping and should not be
-- done often.
--
-- @tparam table target The table to modify.
-- @tparam table new The table which contains the new content.
-- @tparam function identifier A function which take the table entry (either
-- from the `target` or `new` table) and return an unique identifier. The
-- identifier type isn't important as long as `==` works to compare them.
-- @tparam[opt] function merger A function takes the entry to modify as first
-- parameter and the new entry as second. The function must return the merged
-- value. If none is provided, there is no attempt to merge the content.
-- @treturn table The target table (for daisy chaining).
-- @treturn table The new entries.
-- @treturn table The removed entries.
-- @treturn table The updated entries.
-- @staticfct gears.table.diff_merge
-- @usage local output, added, removed, updated = gears.table.diff_merge(
-- output, input, function(v) return v.id end, gears.table.crush,
-- )
function gtable.diff_merge(target, new, identifier, merger)
local n_id, o_id, up = {}, {}, {}
local add, rem = gtable.clone(new, false), gtable.clone(target, false)
for _, v in ipairs(target) do
o_id[identifier(v)] = v
end
for _, v in ipairs(new) do
n_id[identifier(v)] = v
end
for k, v in ipairs(rem) do
if n_id[identifier(v)] then
rem[k] = nil
end
end
for k, v in ipairs(add) do
local id = identifier(v)
local old = o_id[id]
if old then
add[k] = nil
if merger then
o_id[id] = merger(old, v)
table.insert(up, old)
end
else
table.insert(target, v)
end
end
for k, v in ipairs(target) do
local id = identifier(v)
if o_id[id] then
target[k] = o_id[id]
end
end
-- Compact.
rem, add = gtable.from_sparse(rem), gtable.from_sparse(add)
for _, v in ipairs(rem) do
for k, v2 in ipairs(target) do
if v == v2 then
table.remove(target, k)
break
end
end
end
return target, add, rem, up
end
--- Map a function to a table.
--
-- The function is applied to each value on the table, returning a modified

View File

@ -5,6 +5,7 @@
---------------------------------------------------------------------------
local naughty = require("naughty.core")
local gdebug = require("gears.debug")
local capi = {awesome = awesome}
if dbus then
naughty.dbus = require("naughty.dbus")
@ -18,11 +19,48 @@ naughty.container = require("naughty.container")
naughty.action = require("naughty.action")
naughty.notification = require("naughty.notification")
-- Attempt to handle early errors when using the manual screen mode.
--
-- Creating a notification popup before the screens are added won't work. To
-- work around this, the code below initializes some screens. One potential
-- problem is that it could emit enough signal to cause even more errors and
-- lose the original error.
--
-- For example, the following error can be displayed using this fallback:
--
-- screen.connect_signal("scanned", function() foobar() end)
--
local function screen_fallback()
if screen.count() == 0 then
gdebug.print_warning("An error occurred before a scrren was added")
-- Private API to scan for screens now.
if #screen._viewports() == 0 then
screen._scan_quiet()
end
local viewports = screen._viewports()
if #viewports > 0 then
for _, viewport in ipairs(viewports) do
local geo = viewport.geometry
screen.fake_add(geo.x, geo.y, geo.width, geo.height)
end
else
screen.fake_add(0, 0, 640, 480)
end
end
end
-- Handle runtime errors during startup
if capi.awesome.startup_errors then
-- Wait until `rc.lua` is executed before creating the notifications.
-- Otherwise nothing is handling them (yet).
awesome.connect_signal("startup", function()
client.connect_signal("scanning", function()
-- A lot of things have to go wrong for this to happen, but it can.
screen_fallback()
naughty.emit_signal(
"request::display_error", capi.awesome.startup_errors, true
)
@ -32,15 +70,20 @@ end
-- Handle runtime errors after startup
do
local in_error = false
capi.awesome.connect_signal("debug::error", function (err)
-- Make sure we don't go into an endless error loop
if in_error then return end
in_error = true
screen_fallback()
naughty.emit_signal("request::display_error", tostring(err), false)
in_error = false
end)
end
return naughty

View File

@ -44,7 +44,7 @@ local function get_widget_context(self)
end
local context = self._widget_context
local dpi = s.dpi
local dpi = s and s.dpi or 96
if (not context) or context.screen ~= s or context.dpi ~= dpi then
context = {
screen = s,

19
luaa.c
View File

@ -19,14 +19,17 @@
*
*/
/** awesome core API
/** AwesomeWM lifecycle API.
*
* Additionally to the classes described here, one can also use X properties as
* described in @{xproperties}.
* This module contains the functions and signal to manage the lifecycle of the
* AwesomeWM process. It allows to execute code at specific point from the early
* initialization all the way to the last events before exiting or restarting.
*
* Additionally it handles signals for spawn and keyboard related events.
*
* @author Julien Danjou <julien@danjou.info>
* @copyright 2008-2009 Julien Danjou
* @module awesome
* @coreclassmod awesome
*/
/** Register a new xproperty.
@ -150,13 +153,13 @@ extern const struct luaL_Reg awesome_mouse_meta[];
* @signal refresh
*/
/** Awesome is about to enter the event loop.
/** AwesomeWM is about to enter the event loop.
*
* This means all initialization has been done.
* @signal startup
*/
/** Awesome is exiting / about to restart.
/** AwesomeWM is exiting / about to restart.
*
* This signal is emitted in the `atexit` handler as well when awesome
* restarts.
@ -167,8 +170,8 @@ extern const struct luaL_Reg awesome_mouse_meta[];
/** The output status of a screen has changed.
*
* @param output String containing which output has changed.
* @param connection_state String containing the connection status of
* @tparam string output String containing which output has changed.
* @tparam string connection_state String containing the connection status of
* the output: It will be either "Connected", "Disconnected" or
* "Unknown".
* @signal screen::change

View File

@ -47,6 +47,8 @@ OPTIONS
Ajouter un répertoire au chemin de recherche de bibliothèque.
*-a*, *--no-argb*::
N'utilise pas le codage ARGB.
*-m*, *--screen*:: 'off' ou 'on'::
Utiliser "manual" pour exécuter rc.lua avant de créer les écrans.
*-r*, *--replace*::
Remplace le gestionnaire de fenêtres existant.

View File

@ -46,6 +46,8 @@ OPTIONS
Add a directory to the library search path.
*-a*, *--no-argb*::
Don't use ARGB visuals.
*-m*, *--screen*:: 'off' or 'on'::
Use "off" to execute rc.lua before creating the screens.
*-r*, *--replace*::
Replace an existing window manager.

View File

@ -132,6 +132,21 @@
* @table awful.object
*/
/** AwesomeWM is about to scan for existing clients.
*
* Connect to this signal when code needs to be executed after screens are
* initialized, but before clients are added.
*
* @signal scanning
*/
/** AwesomeWM is done scanning for clients.
*
* This is emitted before the `startup` signal and after the `scanning` signal.
*
* @signal scanned
*/
/** When a client gains focus.
* @signal focus
*/
@ -1038,6 +1053,20 @@ DO_CLIENT_SET_STRING_PROPERTY(role)
DO_CLIENT_SET_STRING_PROPERTY(machine)
#undef DO_CLIENT_SET_STRING_PROPERTY
void
client_emit_scanned(void)
{
lua_State *L = globalconf_get_lua_State();
luaA_class_emit_signal(L, &client_class, "scanned", 0);
}
void
client_emit_scanning(void)
{
lua_State *L = globalconf_get_lua_State();
luaA_class_emit_signal(L, &client_class, "scanning", 0);
}
void
client_set_motif_wm_hints(lua_State *L, int cidx, motif_wm_hints_t hints)
{

View File

@ -244,6 +244,8 @@ void client_refresh_partial(client_t *, int16_t, int16_t, uint16_t, uint16_t);
void client_class_setup(lua_State *);
void client_send_configure(client_t *);
void client_find_transient_for(client_t *);
void client_emit_scanned(void);
void client_emit_scanning(void);
drawable_t *client_get_drawable(client_t *, int, int);
drawable_t *client_get_drawable_offset(client_t *, int *, int *);

View File

@ -62,6 +62,37 @@
*/
#define FAKE_SCREEN_XID ((uint32_t) 0xffffffff)
/** AwesomeWM is about to scan for existing screens.
*
* Connect to this signal when code needs to be executed after the Lua context
* is initialized and modules are loaded, but before screens are added.
*
* To manage screens manually, set `screen.automatic_factory = false` and
* connect to the `property::viewports` signal. It is then possible to use
* `screen.fake_add` to create virtual screens. Be careful when using this,
* when done incorrectly, no screens will be created. Using Awesome with zero
* screens is **not** supported.
*
* @signal scanning
* @see property::viewports
* @see screen.fake_add
*/
/** AwesomeWM is done scanning for screens.
*
* Connect to this signal to execute code after the screens have been created,
* but before the clients are added. This signal can also be used to split
* physical screens into multiple virtual screens before the clients (and their
* rules) are executed.
*
* Note that if no screens exist at this point, the fallback code will be
* triggered and the default (detected) screens will be added.
*
* @signal scanned
* @see screen.fake_resize
* @see screen.fake_add
*/
/** Screen is a table where indexes are screen numbers. You can use `screen[1]`
* to get access to the first screen, etc. Alternatively, if RANDR information
* is available, you can use output names for finding screen objects.
@ -94,12 +125,33 @@
* @signal swapped
*/
/** This signal is emitted when the list of physical screen viewport changes.
*
* Each viewport in the list corresponds to a **physical** screen rectangle, which
* is **not** the `viewports` property of the `screen` objects.
*
* @signal property::viewports
* @tparam table viewports
* @see automatic_factory
*/
/**
* The primary screen.
*
* @tfield screen primary
*/
/**
* If `screen` objects are created automatically when new viewports are detected.
*
* Be very, very careful when setting this to false. You might end up with
* no screens. This is **not** supported. Always connect to the `scanned`
* signal to make sure to create a fallback screen if none were created.
*
* @tfield[opt=true] boolean screen.automatic_factory
* @see property::viewports
*/
/**
* The screen coordinates.
*
@ -225,6 +277,8 @@ static void
screen_output_wipe(screen_output_t *output)
{
p_delete(&output->name);
randr_output_array_wipe(&output->outputs);
}
ARRAY_FUNCS(screen_output_t, screen_output, screen_output_wipe)
@ -232,13 +286,6 @@ ARRAY_FUNCS(screen_output_t, screen_output, screen_output_wipe)
static lua_class_t screen_class;
LUA_OBJECT_FUNCS(screen_class, screen_t, screen)
/** Collect a screen. */
static void
screen_wipe(screen_t *s)
{
screen_output_array_wipe(&s->outputs);
}
/** Check if a screen is valid */
static bool
screen_checker(screen_t *s)
@ -297,6 +344,243 @@ screen_deduplicate(lua_State *L, screen_array_t *screens)
}
}
/** Keep track of the screen viewport(s) independently from the screen objects.
*
* A viewport is a collection of `outputs` objects and their associated
* metadata. This structure is copied into Lua and then further extended from
* there. The `id` field allows to differentiate between viewports that share
* the same position and dimensions without having to rely on userdata pointer
* comparison.
*
* Screen objects are widely used by the public API and imply a very "visible"
* concept. A viewport is a subset of what the concerns the "screen" class
* previously handled. It is meant to be used by some low level Lua logic to
* create screens from Lua rather than from C. This is required to increase the
* flexibility of multi-screen setup or when screens are connected and
* disconnected often.
*
* Design rationals:
*
* * The structure is not directly shared with Lua to avoid having to use the
* slow "miss_handler" and unsafe "valid" systems used by other CAPI objects.
* * The `viewport_t` implements a linked-list because its main purpose is to
* offers a deduplication algorithm. Random access is never required.
* * Everything that can be done in Lua is done in Lua.
* * Since the legacy and "new" way to initialize screens share a lot of steps,
* the C code is bent to share as much code as possible. This will reduce the
* "dead code" and improve code coverage by the tests.
*
*/
typedef struct viewport_t
{
bool marked;
int x;
int y;
int width;
int height;
struct viewport_t *next;
screen_t *screen;
screen_output_array_t outputs;
} viewport_t;
static viewport_t *first_screen_viewport = NULL;
static viewport_t *last_screen_viewport = NULL;
static void
luaA_viewport_get_outputs(lua_State *L, viewport_t *a)
{
lua_createtable(L, 0, a ? a->outputs.len : 0);
if (!a)
return;
int count = 1;
foreach(output, a->outputs) {
lua_createtable(L, 3, 0);
lua_pushstring(L, "mm_width");
lua_pushinteger(L, output->mm_width);
lua_settable(L, -3);
lua_pushstring(L, "mm_height");
lua_pushinteger(L, output->mm_height);
lua_settable(L, -3);
lua_pushstring(L, "name");
lua_pushstring(L, output->name);
lua_settable(L, -3);
/* Add to the outputs */
lua_rawseti(L, -2, count++);
}
}
static int
luaA_viewports(lua_State *L)
{
/* All viewports */
lua_newtable(L);
viewport_t *a = first_screen_viewport;
if (!a)
return 1;
int count = 1;
do {
lua_newtable(L);
/* The geometry */
lua_pushstring(L, "geometry");
lua_newtable(L);
lua_pushstring(L, "x");
lua_pushinteger(L, a->x);
lua_settable(L, -3);
lua_pushstring(L, "y");
lua_pushinteger(L, a->y);
lua_settable(L, -3);
lua_pushstring(L, "width");
lua_pushinteger(L, a->width);
lua_settable(L, -3);
lua_pushstring(L, "height");
lua_pushinteger(L, a->height);
lua_settable(L, -3);
/* Add the geometry table to the arguments */
lua_settable(L, -3);
/* Add the outputs table to the arguments */
lua_pushstring(L, "outputs");
luaA_viewport_get_outputs(L, a);
lua_settable(L, -3);
lua_rawseti(L, -2, count++);
} while ((a = a->next));
return 1;
}
/* Give Lua a chance to handle or blacklist a viewport before creating the
* screen object.
*/
static void
viewports_notify(lua_State *L)
{
if (!first_screen_viewport)
return;
luaA_viewports(L);
luaA_class_emit_signal(L, &screen_class, "property::viewports", 1);
}
static viewport_t *
viewport_add(lua_State *L, int x, int y, int w, int h)
{
/* Search existing to avoid having to deduplicate later */
viewport_t *a = first_screen_viewport;
do
{
if (a && a->x == x && a->y == y && a->width == w && a->height == h)
{
a->marked = true;
return a;
}
} while (a && (a = a->next));
viewport_t *node = malloc(sizeof(viewport_t));
node->x = x;
node->y = y;
node->width = w;
node->height = h;
node->next = NULL;
node->screen = NULL;
node->marked = true;
screen_output_array_init(&node->outputs);
if (!first_screen_viewport) {
first_screen_viewport = node;
last_screen_viewport = node;
} else {
last_screen_viewport->next = node;
last_screen_viewport = node;
}
assert(first_screen_viewport && last_screen_viewport);
return node;
}
static void
monitor_unmark(void)
{
viewport_t *a = first_screen_viewport;
if (!a)
return;
do
{
a->marked = false;
} while((a = a->next));
}
static void
viewport_purge(void)
{
viewport_t *cur = first_screen_viewport;
/* Move the head of the list */
while (first_screen_viewport && !first_screen_viewport->marked) {
cur = first_screen_viewport;
first_screen_viewport = cur->next;
foreach(existing_screen, globalconf.screens)
if ((*existing_screen)->viewport == cur)
(*existing_screen)->viewport = NULL;
screen_output_array_wipe(&cur->outputs);
free(cur);
}
if (!first_screen_viewport) {
last_screen_viewport = NULL;
return;
}
cur = first_screen_viewport;
/* Drop unmarked entries */
do {
if (cur->next && !cur->next->marked) {
viewport_t *tmp = cur->next;
cur->next = cur->next->next;
if (tmp == last_screen_viewport)
last_screen_viewport = cur;
foreach(existing_screen, globalconf.screens)
if ((*existing_screen)->viewport == tmp)
(*existing_screen)->viewport = NULL;
screen_output_array_wipe(&tmp->outputs);
free(tmp);
} else
cur = cur->next;
} while(cur);
}
static screen_t *
screen_add(lua_State *L, screen_array_t *screens)
{
@ -309,6 +593,43 @@ screen_add(lua_State *L, screen_array_t *screens)
/* Monitors were introduced in RandR 1.5 */
#ifdef XCB_RANDR_GET_MONITORS
static screen_output_t
screen_get_randr_output(lua_State *L, xcb_randr_monitor_info_iterator_t *it)
{
screen_output_t output;
xcb_randr_output_t *randr_outputs;
xcb_get_atom_name_cookie_t name_c;
xcb_get_atom_name_reply_t *name_r;
output.mm_width = it->data->width_in_millimeters;
output.mm_height = it->data->height_in_millimeters;
name_c = xcb_get_atom_name_unchecked(globalconf.connection, it->data->name);
name_r = xcb_get_atom_name_reply(globalconf.connection, name_c, NULL);
if (name_r) {
const char *name = xcb_get_atom_name_name(name_r);
size_t len = xcb_get_atom_name_name_length(name_r);
output.name = memcpy(p_new(char *, len + 1), name, len);
output.name[len] = '\0';
p_delete(&name_r);
} else {
output.name = a_strdup("unknown");
}
randr_output_array_init(&output.outputs);
randr_outputs = xcb_randr_monitor_info_outputs(it->data);
for(int i = 0; i < xcb_randr_monitor_info_outputs_length(it->data); i++) {
randr_output_array_append(&output.outputs, randr_outputs[i]);
}
return output;
}
static void
screen_scan_randr_monitors(lua_State *L, screen_array_t *screens)
{
@ -325,44 +646,32 @@ screen_scan_randr_monitors(lua_State *L, screen_array_t *screens)
monitor_iter.rem; xcb_randr_monitor_info_next(&monitor_iter))
{
screen_t *new_screen;
screen_output_t output;
xcb_randr_output_t *randr_outputs;
xcb_get_atom_name_cookie_t name_c;
xcb_get_atom_name_reply_t *name_r;
if(!xcb_randr_monitor_info_outputs_length(monitor_iter.data))
continue;
screen_output_t output = screen_get_randr_output(L, &monitor_iter);
viewport_t *viewport = viewport_add(L,
monitor_iter.data->x,
monitor_iter.data->y,
monitor_iter.data->width,
monitor_iter.data->height
);
screen_output_array_append(&viewport->outputs, output);
if (globalconf.ignore_screens)
continue;
new_screen = screen_add(L, screens);
viewport->screen = new_screen;
new_screen->viewport = viewport;
new_screen->geometry.x = monitor_iter.data->x;
new_screen->geometry.y = monitor_iter.data->y;
new_screen->geometry.width = monitor_iter.data->width;
new_screen->geometry.height = monitor_iter.data->height;
new_screen->xid = monitor_iter.data->name;
output.mm_width = monitor_iter.data->width_in_millimeters;
output.mm_height = monitor_iter.data->height_in_millimeters;
name_c = xcb_get_atom_name_unchecked(globalconf.connection, monitor_iter.data->name);
name_r = xcb_get_atom_name_reply(globalconf.connection, name_c, NULL);
if (name_r) {
const char *name = xcb_get_atom_name_name(name_r);
size_t len = xcb_get_atom_name_name_length(name_r);
output.name = memcpy(p_new(char *, len + 1), name, len);
output.name[len] = '\0';
p_delete(&name_r);
} else {
output.name = a_strdup("unknown");
}
randr_output_array_init(&output.outputs);
randr_outputs = xcb_randr_monitor_info_outputs(monitor_iter.data);
for(int i = 0; i < xcb_randr_monitor_info_outputs_length(monitor_iter.data); i++) {
randr_output_array_append(&output.outputs, randr_outputs[i]);
}
screen_output_array_append(&new_screen->outputs, output);
}
p_delete(&monitors_r);
@ -374,6 +683,39 @@ screen_scan_randr_monitors(lua_State *L, screen_array_t *screens)
}
#endif
static void
screen_get_randr_crtcs_outputs(lua_State *L, xcb_randr_get_crtc_info_reply_t *crtc_info_r, screen_output_array_t *outputs)
{
xcb_randr_output_t *randr_outputs = xcb_randr_get_crtc_info_outputs(crtc_info_r);
for(int j = 0; j < xcb_randr_get_crtc_info_outputs_length(crtc_info_r); j++)
{
xcb_randr_get_output_info_cookie_t output_info_c = xcb_randr_get_output_info(globalconf.connection, randr_outputs[j], XCB_CURRENT_TIME);
xcb_randr_get_output_info_reply_t *output_info_r = xcb_randr_get_output_info_reply(globalconf.connection, output_info_c, NULL);
screen_output_t output;
if (!output_info_r) {
warn("RANDR GetOutputInfo failed; this should not be possible");
continue;
}
int len = xcb_randr_get_output_info_name_length(output_info_r);
/* name is not NULL terminated */
char *name = memcpy(p_new(char *, len + 1), xcb_randr_get_output_info_name(output_info_r), len);
name[len] = '\0';
output.name = name;
output.mm_width = output_info_r->mm_width;
output.mm_height = output_info_r->mm_height;
randr_output_array_init(&output.outputs);
randr_output_array_append(&output.outputs, randr_outputs[j]);
screen_output_array_append(outputs, output);
p_delete(&output_info_r);
}
}
static void
screen_scan_randr_crtcs(lua_State *L, screen_array_t *screens)
{
@ -406,45 +748,34 @@ screen_scan_randr_crtcs(lua_State *L, screen_array_t *screens)
if(!xcb_randr_get_crtc_info_outputs_length(crtc_info_r))
continue;
viewport_t *viewport = viewport_add(L,
crtc_info_r->x,
crtc_info_r->y,
crtc_info_r->width,
crtc_info_r->height
);
screen_get_randr_crtcs_outputs(L, crtc_info_r, &viewport->outputs);
if (globalconf.ignore_screens)
{
p_delete(&crtc_info_r);
continue;
}
/* Prepare the new screen */
screen_t *new_screen = screen_add(L, screens);
viewport->screen = new_screen;
new_screen->viewport = viewport;
new_screen->geometry.x = crtc_info_r->x;
new_screen->geometry.y = crtc_info_r->y;
new_screen->geometry.width= crtc_info_r->width;
new_screen->geometry.height= crtc_info_r->height;
new_screen->xid = randr_crtcs[i];
xcb_randr_output_t *randr_outputs = xcb_randr_get_crtc_info_outputs(crtc_info_r);
for(int j = 0; j < xcb_randr_get_crtc_info_outputs_length(crtc_info_r); j++)
{
xcb_randr_get_output_info_cookie_t output_info_c = xcb_randr_get_output_info(globalconf.connection, randr_outputs[j], XCB_CURRENT_TIME);
xcb_randr_get_output_info_reply_t *output_info_r = xcb_randr_get_output_info_reply(globalconf.connection, output_info_c, NULL);
screen_output_t output;
if (!output_info_r) {
warn("RANDR GetOutputInfo failed; this should not be possible");
continue;
}
int len = xcb_randr_get_output_info_name_length(output_info_r);
/* name is not NULL terminated */
char *name = memcpy(p_new(char *, len + 1), xcb_randr_get_output_info_name(output_info_r), len);
name[len] = '\0';
output.name = name;
output.mm_width = output_info_r->mm_width;
output.mm_height = output_info_r->mm_height;
randr_output_array_init(&output.outputs);
randr_output_array_append(&output.outputs, randr_outputs[j]);
screen_output_array_append(&new_screen->outputs, output);
p_delete(&output_info_r);
if (A_STREQ(name, "default"))
{
/* Detect the older NVIDIA blobs */
foreach(output, new_screen->viewport->outputs) {
if (A_STREQ(output->name, "default")) {
/* non RandR 1.2+ X driver don't return any usable multihead
* data. I'm looking at you, nvidia binary blob!
*/
@ -453,9 +784,12 @@ screen_scan_randr_crtcs(lua_State *L, screen_array_t *screens)
/* Get rid of the screens that we already created */
foreach(screen, *screens)
luaA_object_unref(L, *screen);
screen_array_wipe(screens);
screen_array_init(screens);
p_delete(&screen_res_r);
return;
}
}
@ -485,6 +819,7 @@ screen_scan_randr(lua_State *L, screen_array_t *screens)
if(!version_reply)
return;
major_version = version_reply->major_version;
minor_version = version_reply->minor_version;
p_delete(&version_reply);
@ -510,7 +845,7 @@ screen_scan_randr(lua_State *L, screen_array_t *screens)
else
screen_scan_randr_crtcs(L, screens);
if (screens->len == 0)
if (screens->len == 0 && !globalconf.ignore_screens)
{
/* Scanning failed, disable randr again */
xcb_randr_select_input(globalconf.connection,
@ -556,7 +891,19 @@ screen_scan_xinerama(lua_State *L, screen_array_t *screens)
for(int screen = 0; screen < xinerama_screen_number; screen++)
{
viewport_t *viewport = viewport_add(L,
xsi[screen].x_org,
xsi[screen].y_org,
xsi[screen].width,
xsi[screen].height
);
if (globalconf.ignore_screens)
continue;
screen_t *s = screen_add(L, screens);
viewport->screen = s;
s->viewport = viewport;
s->geometry.x = xsi[screen].x_org;
s->geometry.y = xsi[screen].y_org;
s->geometry.width = xsi[screen].width;
@ -569,7 +916,20 @@ screen_scan_xinerama(lua_State *L, screen_array_t *screens)
static void screen_scan_x11(lua_State *L, screen_array_t *screens)
{
xcb_screen_t *xcb_screen = globalconf.screen;
viewport_t *viewport = viewport_add(L,
0,
0,
xcb_screen->width_in_pixels,
xcb_screen->height_in_pixels
);
if (globalconf.ignore_screens)
return;
screen_t *s = screen_add(L, screens);
viewport->screen = s;
s->viewport = viewport;
s->geometry.x = 0;
s->geometry.y = 0;
s->geometry.width = xcb_screen->width_in_pixels;
@ -586,21 +946,36 @@ screen_added(lua_State *L, screen_t *screen)
lua_pop(L, 1);
}
/** Get screens informations and fill global configuration.
*/
void
screen_scan(void)
screen_emit_scanned(void)
{
lua_State *L = globalconf_get_lua_State();
luaA_class_emit_signal(L, &screen_class, "scanned", 0);
}
void
screen_emit_scanning(void)
{
lua_State *L = globalconf_get_lua_State();
luaA_class_emit_signal(L, &screen_class, "scanning", 0);
}
static void
screen_scan_common(bool quiet)
{
lua_State *L;
L = globalconf_get_lua_State();
monitor_unmark();
screen_scan_randr(L, &globalconf.screens);
if (globalconf.screens.len == 0)
screen_scan_xinerama(L, &globalconf.screens);
if (globalconf.screens.len == 0)
screen_scan_x11(L, &globalconf.screens);
check(globalconf.screens.len > 0);
check(globalconf.screens.len > 0 || globalconf.ignore_screens);
screen_deduplicate(L, &globalconf.screens);
@ -608,9 +983,30 @@ screen_scan(void)
screen_added(L, *screen);
}
viewport_purge();
if (!quiet)
viewports_notify(L);
screen_update_primary();
}
/** Get screens informations and fill global configuration.
*/
void
screen_scan(void)
{
screen_emit_scanning();
screen_scan_common(false);
}
static int
luaA_scan_quiet(lua_State *L)
{
screen_scan_common(true);
return 0;
}
/* Called when a screen is removed, removes references to the old screen */
static void
screen_removed(lua_State *L, int sidx)
@ -629,6 +1025,15 @@ screen_removed(lua_State *L, int sidx)
}
}
void screen_cleanup(void)
{
while(globalconf.screens.len)
screen_array_take(&globalconf.screens, 0);
monitor_unmark();
viewport_purge();
}
static void
screen_modified(screen_t *existing_screen, screen_t *other_screen)
{
@ -644,25 +1049,36 @@ screen_modified(screen_t *existing_screen, screen_t *other_screen)
screen_update_workarea(existing_screen);
}
bool outputs_changed = existing_screen->outputs.len != other_screen->outputs.len;
if(!outputs_changed)
for(int i = 0; i < existing_screen->outputs.len; i++) {
screen_output_t *existing_output = &existing_screen->outputs.tab[i];
screen_output_t *other_output = &other_screen->outputs.tab[i];
const int other_len = other_screen->viewport ?
other_screen->viewport->outputs.len : 0;
const int existing_len = existing_screen->viewport ?
existing_screen->viewport->outputs.len : 0;
bool outputs_changed = (!(existing_screen->viewport && other_screen->viewport))
|| existing_len != other_len;
if(existing_screen->viewport && other_screen->viewport && !outputs_changed)
for(int i = 0; i < existing_screen->viewport->outputs.len; i++) {
screen_output_t *existing_output = &existing_screen->viewport->outputs.tab[i];
screen_output_t *other_output = &other_screen->viewport->outputs.tab[i];
outputs_changed |= existing_output->mm_width != other_output->mm_width;
outputs_changed |= existing_output->mm_height != other_output->mm_height;
outputs_changed |= A_STRNEQ(existing_output->name, other_output->name);
}
/* Brute-force update the outputs by swapping */
screen_output_array_t tmp = other_screen->outputs;
other_screen->outputs = existing_screen->outputs;
existing_screen->outputs = tmp;
if(existing_screen->viewport || other_screen->viewport) {
viewport_t *tmp = other_screen->viewport;
other_screen->viewport = existing_screen->viewport;
if(outputs_changed) {
luaA_object_push(L, existing_screen);
luaA_object_emit_signal(L, -1, "property::outputs", 0);
lua_pop(L, 1);
existing_screen->viewport = tmp;
if(outputs_changed) {
luaA_object_push(L, existing_screen);
luaA_object_emit_signal(L, -1, "property::outputs", 0);
lua_pop(L, 1);
}
}
}
@ -671,6 +1087,8 @@ screen_refresh(gpointer unused)
{
globalconf.screen_refresh_pending = false;
monitor_unmark();
screen_array_t new_screens;
screen_array_t removed_screens;
lua_State *L = globalconf_get_lua_State();
@ -682,6 +1100,10 @@ screen_refresh(gpointer unused)
else
screen_scan_randr_crtcs(L, &new_screens);
viewport_purge();
viewports_notify(L);
screen_deduplicate(L, &new_screens);
/* Running without any screens at all is no fun. */
@ -837,10 +1259,10 @@ screen_coord_in_screen(screen_t *s, int x, int y)
bool
screen_area_in_screen(screen_t *s, area_t geom)
{
return (geom.x < s->geometry.x + s->geometry.width)
&& (geom.x + geom.width > s->geometry.x )
&& (geom.y < s->geometry.y + s->geometry.height)
&& (geom.y + geom.height > s->geometry.y);
return (geom.x < s->geometry.x + s->geometry.width)
&& (geom.x + geom.width > s->geometry.x )
&& (geom.y < s->geometry.y + s->geometry.height)
&& (geom.y + geom.height > s->geometry.y);
}
void screen_update_workarea(screen_t *screen)
@ -1022,10 +1444,11 @@ screen_update_primary(void)
foreach(screen, globalconf.screens)
{
foreach(output, (*screen)->outputs)
foreach (randr_output, output->outputs)
if (*randr_output == primary->output)
primary_screen = *screen;
if ((*screen)->viewport)
foreach(output, (*screen)->viewport->outputs)
foreach (randr_output, output->outputs)
if (*randr_output == primary->output)
primary_screen = *screen;
}
p_delete(&primary);
@ -1077,11 +1500,18 @@ luaA_screen_module_index(lua_State *L)
{
if(A_STREQ(name, "primary"))
return luaA_object_push(L, screen_get_primary());
else if (A_STREQ(name, "automatic_factory"))
{
lua_pushboolean(L, !globalconf.ignore_screens);
return 1;
}
foreach(screen, globalconf.screens)
foreach(output, (*screen)->outputs)
if(A_STREQ(output->name, name))
return luaA_object_push(L, *screen);
if ((*screen)->viewport)
foreach(output, (*screen)->viewport->outputs)
if(A_STREQ(output->name, name))
return luaA_object_push(L, *screen);
luaA_warn(L, "Unknown screen output name: %s", name);
lua_pushnil(L);
return 1;
@ -1090,6 +1520,28 @@ luaA_screen_module_index(lua_State *L)
return luaA_object_push(L, luaA_checkscreen(L, 2));
}
static int
luaA_screen_module_newindex(lua_State *L)
{
const char *buf = luaL_checkstring(L, 2);
if (A_STREQ(buf, "automatic_factory"))
{
globalconf.ignore_screens = !luaA_checkboolean(L, 3);
/* It *can* be useful if screens are added/removed later, but generally,
* setting this should be done before screens are added
*/
if (globalconf.ignore_screens && !globalconf.no_auto_screen)
luaA_warn(L,
"Setting automatic_factory only makes sense when AwesomeWM is"
" started with `--screen off`"
);
}
return luaA_default_newindex(L);
}
/** Iterate over screens.
* @usage
* for s in screen do
@ -1126,18 +1578,8 @@ luaA_screen_get_index(lua_State *L, screen_t *s)
static int
luaA_screen_get_outputs(lua_State *L, screen_t *s)
{
lua_createtable(L, 0, s->outputs.len);
foreach(output, s->outputs)
{
lua_createtable(L, 2, 0);
luaA_viewport_get_outputs(L, s->viewport);
lua_pushinteger(L, output->mm_width);
lua_setfield(L, -2, "mm_width");
lua_pushinteger(L, output->mm_height);
lua_setfield(L, -2, "mm_height");
lua_setfield(L, -2, output->name);
}
/* The table of tables we created. */
return 1;
}
@ -1312,8 +1754,10 @@ screen_class_setup(lua_State *L)
{
LUA_CLASS_METHODS(screen)
{ "count", luaA_screen_count },
{ "_viewports", luaA_viewports },
{ "_scan_quiet", luaA_scan_quiet },
{ "__index", luaA_screen_module_index },
{ "__newindex", luaA_default_newindex },
{ "__newindex", luaA_screen_module_newindex },
{ "__call", luaA_screen_module_call },
{ "fake_add", luaA_screen_fake_add },
{ NULL, NULL }
@ -1331,7 +1775,7 @@ screen_class_setup(lua_State *L)
luaA_class_setup(L, &screen_class, "screen", NULL,
(lua_class_allocator_t) screen_new,
(lua_class_collector_t) screen_wipe,
(lua_class_collector_t) NULL,
(lua_class_checker_t) screen_checker,
luaA_class_index_miss_property, luaA_class_newindex_miss_property,
screen_methods, screen_meta);

View File

@ -39,8 +39,8 @@ struct a_screen
area_t geometry;
/** Screen workarea */
area_t workarea;
/** The screen outputs informations */
screen_output_array_t outputs;
/** Opaque pointer to the psysical geometry */
struct viewport_t *viewport;
/** Some XID identifying this screen */
uint32_t xid;
};
@ -57,6 +57,9 @@ void screen_update_primary(void);
void screen_update_workarea(screen_t *);
screen_t *screen_get_primary(void);
void screen_schedule_refresh(void);
void screen_emit_scanned(void);
void screen_emit_scanning(void);
void screen_cleanup(void);
screen_t *luaA_checkscreen(lua_State *, int);

View File

@ -228,7 +228,7 @@ local function iter_scr(_, _, s)
end
end
function screen._areas()
function screen._viewports()
return {}
end

View File

@ -23,16 +23,19 @@ Usage: $0 [OPTION]... [FILE]...
Options:
-v: verbose mode
-W: warnings become errors
-m: Use --screen off
-h: show this help
EOF
exit "$1"
}
fail_on_warning=
manual_screens=
verbose=${VERBOSE:-0}
while getopts vWh opt; do
while getopts vWmh opt; do
case $opt in
v) verbose=1 ;;
W) fail_on_warning=1 ;;
m) manual_screens=" --screen off" ;;
h) usage 0 ;;
*) usage 64 ;;
esac
@ -105,7 +108,9 @@ fi
# Add test dir (for _runner.lua).
# shellcheck disable=SC2206
awesome_options=($AWESOME_OPTIONS --search lib --search "$this_dir")
awesome_options=($AWESOME_OPTIONS $manual_screens --search lib --search "$this_dir")
awesome_options+=(--screen off)
# Cleanup on errors / aborting.
cleanup() {
@ -172,6 +177,7 @@ fi
# Start awesome.
start_awesome() {
cd "$build_dir"
# Kill awesome after $TEST_TIMEOUT seconds (e.g. for errors during test setup).
# SOURCE_DIRECTORY is used by .luacov.
DISPLAY="$D" SOURCE_DIRECTORY="$source_dir" \

View File

@ -64,6 +64,34 @@ local steps = {
return true
end
end,
-- Make sure the error code still works when all screens are gone.
function()
while screen.count() > 0 do
screen[1]:fake_remove()
end
-- Don't make the test fail.
local called = false
require("gears.debug").print_warning = function() called = true end
-- Cause an error in a protected call!
awesome.emit_signal("debug::error", "err")
assert(called)
return true
end,
-- Test the `automatic_factory` getter and setter.
function()
assert(type(screen.automatic_factory) == "boolean")
local orig = screen.automatic_factory
screen.automatic_factory = not orig
assert(screen.automatic_factory ~= orig)
assert(type(screen.automatic_factory) == "boolean")
screen.automatic_factory = not screen.automatic_factory
assert(screen.automatic_factory == orig)
return true
end
}

View File

@ -4,8 +4,22 @@ local runner = require("_runner")
local wibox = require("wibox")
local awful = require("awful")
-- Make sure we have at least two screens to test this on
screen.fake_add(-100, -100, 50, 50)
-- Make sure we have at least two screens to test this on.
local origin_width = screen[1].geometry.width
screen[1]:fake_resize(
screen[1].geometry.x,
screen[1].geometry.y,
origin_width/2,
screen[1].geometry.height
)
screen.fake_add(
screen[1].geometry.x+origin_width/2,
screen[1].geometry.y,
origin_width/2,
screen[1].geometry.height
)
assert(screen.count() == 2)
-- Each screen gets a wibox displaying our only_on_screen widget
@ -116,7 +130,12 @@ table.insert(steps, function()
for s in screen do
assert(not widget_visible_on(s))
end
screen.fake_add(-100, -100, 50, 50)
screen.fake_add(
screen[1].geometry.x+origin_width/2,
screen[1].geometry.y,
origin_width/2,
screen[1].geometry.height
)
return true
end)
table.insert(steps, function()