@ -1,10 +1,12 @@
# define _POSIX_C_SOURCE 200809L
# include <assert.h>
# include <ctype.h>
# include <getopt.h>
# include <stdbool.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include < time .h>
# include < strings .h>
# include <wayland-client.h>
# include "background-image.h"
# include "cairo.h"
@ -14,49 +16,44 @@
# include "wlr-layer-shell-unstable-v1-client-protocol.h"
# include "xdg-output-unstable-v1-client-protocol.h"
struct swaybg_state ;
struct swaybg_args {
const char * output ;
const char * path ;
enum background_mode mode ;
const char * fallback ;
struct swaybg_state {
struct wl_display * display ;
struct wl_compositor * compositor ;
struct wl_shm * shm ;
struct zwlr_layer_shell_v1 * layer_shell ;
struct zxdg_output_manager_v1 * xdg_output_manager ;
struct wl_list configs ; // struct swaybg_output_config::link
struct wl_list outputs ; // struct swaybg_output::link
bool run_display ;
} ;
struct swaybg_ context {
uint32_t color ;
struct swaybg_ output_config {
char * output ;
cairo_surface_t * image ;
enum background_mode mode ;
uint32_t color ;
struct wl_list link ;
} ;
struct swaybg_output {
uint32_t wl_name ;
struct wl_output * wl_output ;
struct zxdg_output_v1 * xdg_output ;
struct swaybg_state * stat e;
struct wl_list link ;
char * nam e;
char * identifier ;
int32_t scale ;
} ;
struct swaybg_state {
const struct swaybg_args * args ;
struct swaybg_context context ;
struct wl_display * display ;
struct wl_compositor * compositor ;
struct wl_shm * shm ;
struct wl_list outputs ;
struct zwlr_layer_shell_v1 * layer_shell ;
struct zxdg_output_manager_v1 * xdg_output_manager ;
struct swaybg_state * state ;
struct swaybg_output_config * config ;
struct swaybg_output * output ;
struct wl_surface * surface ;
struct wl_region * input_region ;
struct zwlr_layer_surface_v1 * layer_surface ;
bool run_display ;
uint32_t width , height ;
struct pool_buffer buffers [ 2 ] ;
struct pool_buffer * current_buffer ;
uint32_t width , height ;
int32_t scale ;
struct wl_list link ;
} ;
bool is_valid_color ( const char * color ) {
@ -77,68 +74,82 @@ bool is_valid_color(const char *color) {
return true ;
}
static void render_frame ( struct swaybg_ state * state ) {
int buffer_width = state- > width * state - > output - > scale ,
buffer_height = state - > height * state - > output - > scale ;
state - > current_buffer = get_next_buffer ( state - > shm ,
state - > buffers , buffer_width , buffer_height ) ;
if ( ! state - > current_buffer ) {
static void render_frame ( struct swaybg_ output * output ) {
int buffer_width = output- > width * output - > scale ,
buffer_height = output - > height * output - > scale ;
output - > current_buffer = get_next_buffer ( output - > state - > shm ,
output - > buffers , buffer_width , buffer_height ) ;
if ( ! output - > current_buffer ) {
return ;
}
cairo_t * cairo = state - > current_buffer - > cairo ;
cairo_t * cairo = output - > current_buffer - > cairo ;
cairo_save ( cairo ) ;
cairo_set_operator ( cairo , CAIRO_OPERATOR_CLEAR ) ;
cairo_paint ( cairo ) ;
cairo_restore ( cairo ) ;
if ( state- > args - > mode = = BACKGROUND_MODE_SOLID_COLOR ) {
cairo_set_source_u32 ( cairo , state- > context . color ) ;
if ( output- > config - > mode = = BACKGROUND_MODE_SOLID_COLOR ) {
cairo_set_source_u32 ( cairo , output- > config - > color ) ;
cairo_paint ( cairo ) ;
} else {
if ( state- > args - > fallback & & state - > context . color ) {
cairo_set_source_u32 ( cairo , state- > context . color ) ;
if ( output- > config - > color ) {
cairo_set_source_u32 ( cairo , output- > config - > color ) ;
cairo_paint ( cairo ) ;
}
render_background_image ( cairo , state- > context . image ,
state- > args - > mode , buffer_width , buffer_height ) ;
render_background_image ( cairo , output- > config - > image ,
output- > config - > mode , buffer_width , buffer_height ) ;
}
wl_surface_set_buffer_scale ( state - > surface , state - > output - > scale ) ;
wl_surface_attach ( state- > surface , state - > current_buffer - > buffer , 0 , 0 ) ;
wl_surface_damage_buffer ( state - > surface , 0 , 0 , INT32_MAX , INT32_MAX ) ;
wl_surface_commit ( state - > surface ) ;
wl_surface_set_buffer_scale ( output - > surface , output - > scale ) ;
wl_surface_attach ( output- > surface , output - > current_buffer - > buffer , 0 , 0 ) ;
wl_surface_damage_buffer ( output - > surface , 0 , 0 , INT32_MAX , INT32_MAX ) ;
wl_surface_commit ( output - > surface ) ;
}
static bool prepare_context ( struct swaybg_state * state ) {
if ( state - > args - > mode = = BACKGROUND_MODE_SOLID_COLOR ) {
state - > context . color = parse_color ( state - > args - > path ) ;
return is_valid_color ( state - > args - > path ) ;
static void destroy_swaybg_output_config ( struct swaybg_output_config * config ) {
if ( ! config ) {
return ;
}
if ( state - > args - > fallback & & is_valid_color ( state - > args - > fallback ) ) {
state - > context . color = parse_color ( state - > args - > fallback ) ;
wl_list_remove ( & config - > link ) ;
free ( config - > output ) ;
free ( config ) ;
}
static void destroy_swaybg_output ( struct swaybg_output * output ) {
if ( ! output ) {
return ;
}
if ( ! ( state - > context . image = load_background_image ( state - > args - > path ) ) ) {
return false ;
wl_list_remove ( & output - > link ) ;
if ( output - > layer_surface ! = NULL ) {
zwlr_layer_surface_v1_destroy ( output - > layer_surface ) ;
}
return true ;
if ( output - > surface ! = NULL ) {
wl_surface_destroy ( output - > surface ) ;
}
zxdg_output_v1_destroy ( output - > xdg_output ) ;
wl_output_destroy ( output - > wl_output ) ;
destroy_buffer ( & output - > buffers [ 0 ] ) ;
destroy_buffer ( & output - > buffers [ 1 ] ) ;
free ( output - > name ) ;
free ( output - > identifier ) ;
free ( output ) ;
}
static void layer_surface_configure ( void * data ,
struct zwlr_layer_surface_v1 * surface ,
uint32_t serial , uint32_t width , uint32_t height ) {
struct swaybg_state * state = data ;
state - > width = width ;
state - > height = height ;
struct swaybg_ output * output = data ;
output - > width = width ;
output - > height = height ;
zwlr_layer_surface_v1_ack_configure ( surface , serial ) ;
render_frame ( state ) ;
render_frame ( output ) ;
}
static void layer_surface_closed ( void * data ,
struct zwlr_layer_surface_v1 * surface ) {
struct swaybg_state * state = data ;
zwlr_layer_surface_v1_destroy ( state - > layer_surface ) ;
wl_surface_destroy ( state - > surface ) ;
wl_region_destroy ( state - > input_region ) ;
state - > run_display = false ;
struct swaybg_output * output = data ;
sway_log ( SWAY_DEBUG , " Destroying output %s (%s) " ,
output - > name , output - > identifier ) ;
destroy_swaybg_output ( output ) ;
}
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
@ -164,12 +175,9 @@ static void output_done(void *data, struct wl_output *output) {
static void output_scale ( void * data , struct wl_output * wl_output ,
int32_t scale ) {
struct swaybg_output * output = data ;
struct swaybg_state * state = output - > state ;
output - > scale = scale ;
if ( state - > output = = output & & state - > run_display ) {
render_frame ( state ) ;
if ( output - > state - > run_display & & output - > width > 0 & & output - > height > 0 ) {
render_frame ( output ) ;
}
}
@ -190,24 +198,91 @@ static void xdg_output_handle_logical_size(void *data,
// Who cares
}
static void find_config ( struct swaybg_output * output , const char * name ) {
struct swaybg_output_config * config = NULL ;
wl_list_for_each ( config , & output - > state - > configs , link ) {
if ( strcmp ( config - > output , name ) = = 0 ) {
output - > config = config ;
return ;
} else if ( ! output - > config & & strcmp ( config - > output , " * " ) = = 0 ) {
output - > config = config ;
}
}
}
static void xdg_output_handle_name ( void * data ,
struct zxdg_output_v1 * xdg_output , const char * name ) {
struct swaybg_output * output = data ;
struct swaybg_state * state = output - > state ;
if ( strcmp ( name , state - > args - > output ) = = 0 ) {
assert ( state - > output = = NULL ) ;
state - > output = output ;
output - > name = strdup ( name ) ;
// If description was sent first, the config may already be populated. If
// there is an identifier config set, keep it.
if ( ! output - > config | | strcmp ( output - > config - > output , " * " ) = = 0 ) {
find_config ( output , name ) ;
}
}
static void xdg_output_handle_description ( void * data ,
struct zxdg_output_v1 * xdg_output , const char * description ) {
// Who cares
struct swaybg_output * output = data ;
// wlroots currently sets the description to `make model serial (name)`
// If this changes in the future, this will need to be modified.
char * paren = strrchr ( description , ' ( ' ) ;
if ( paren ) {
size_t length = paren - description ;
output - > identifier = malloc ( length ) ;
if ( ! output - > identifier ) {
sway_log ( SWAY_ERROR , " Failed to allocate output identifier " ) ;
return ;
}
strncpy ( output - > identifier , description , length ) ;
output - > identifier [ length - 1 ] = ' \0 ' ;
find_config ( output , output - > identifier ) ;
}
}
static void create_layer_surface ( struct swaybg_output * output ) {
output - > surface = wl_compositor_create_surface ( output - > state - > compositor ) ;
assert ( output - > surface ) ;
// Empty input region
struct wl_region * input_region =
wl_compositor_create_region ( output - > state - > compositor ) ;
assert ( input_region ) ;
wl_surface_set_input_region ( output - > surface , input_region ) ;
wl_region_destroy ( input_region ) ;
output - > layer_surface = zwlr_layer_shell_v1_get_layer_surface (
output - > state - > layer_shell , output - > surface , output - > wl_output ,
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND , " wallpaper " ) ;
assert ( output - > layer_surface ) ;
zwlr_layer_surface_v1_set_size ( output - > layer_surface , 0 , 0 ) ;
zwlr_layer_surface_v1_set_anchor ( output - > layer_surface ,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ) ;
zwlr_layer_surface_v1_set_exclusive_zone ( output - > layer_surface , - 1 ) ;
zwlr_layer_surface_v1_add_listener ( output - > layer_surface ,
& layer_surface_listener , output ) ;
wl_surface_commit ( output - > surface ) ;
}
static void xdg_output_handle_done ( void * data ,
struct zxdg_output_v1 * xdg_output ) {
// Who cares
struct swaybg_output * output = data ;
if ( ! output - > config ) {
sway_log ( SWAY_DEBUG , " Could not find config for output %s (%s) " ,
output - > name , output - > identifier ) ;
destroy_swaybg_output ( output ) ;
} else if ( ! output - > layer_surface ) {
sway_log ( SWAY_DEBUG , " Found config %s for output %s (%s) " ,
output - > config - > output , output - > name , output - > identifier ) ;
create_layer_surface ( output ) ;
}
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
@ -229,10 +304,18 @@ static void handle_global(void *data, struct wl_registry *registry,
} else if ( strcmp ( interface , wl_output_interface . name ) = = 0 ) {
struct swaybg_output * output = calloc ( 1 , sizeof ( struct swaybg_output ) ) ;
output - > state = state ;
output - > wl_name = name ;
output - > wl_output =
wl_registry_bind ( registry , name , & wl_output_interface , 3 ) ;
wl_output_add_listener ( output - > wl_output , & output_listener , output ) ;
wl_list_insert ( & state - > outputs , & output - > link ) ;
if ( state - > run_display ) {
output - > xdg_output = zxdg_output_manager_v1_get_xdg_output (
state - > xdg_output_manager , output - > wl_output ) ;
zxdg_output_v1_add_listener ( output - > xdg_output ,
& xdg_output_listener , output ) ;
}
} else if ( strcmp ( interface , zwlr_layer_shell_v1_interface . name ) = = 0 ) {
state - > layer_shell =
wl_registry_bind ( registry , name , & zwlr_layer_shell_v1_interface , 1 ) ;
@ -244,7 +327,16 @@ static void handle_global(void *data, struct wl_registry *registry,
static void handle_global_remove ( void * data , struct wl_registry * registry ,
uint32_t name ) {
// who cares
struct swaybg_state * state = data ;
struct swaybg_output * output , * tmp ;
wl_list_for_each_safe ( output , tmp , & state - > outputs , link ) {
if ( output - > wl_name = = name ) {
sway_log ( SWAY_DEBUG , " Destroying output %s (%s) " ,
output - > name , output - > identifier ) ;
destroy_swaybg_output ( output ) ;
break ;
}
}
}
static const struct wl_registry_listener registry_listener = {
@ -252,32 +344,159 @@ static const struct wl_registry_listener registry_listener = {
. global_remove = handle_global_remove ,
} ;
int main ( int argc , const char * * argv ) {
sway_log_init ( SWAY_DEBUG , NULL ) ;
struct swaybg_args args = { 0 } ;
struct swaybg_state state = { . args = & args } ;
wl_list_init ( & state . outputs ) ;
static bool store_swaybg_output_config ( struct swaybg_state * state ,
struct swaybg_output_config * config ) {
struct swaybg_output_config * oc = NULL ;
wl_list_for_each ( oc , & state - > configs , link ) {
if ( strcmp ( config - > output , oc - > output ) = = 0 ) {
// Merge on top
if ( config - > image ) {
free ( oc - > image ) ;
oc - > image = config - > image ;
config - > image = NULL ;
}
if ( config - > color ) {
oc - > color = config - > color ;
}
if ( config - > mode ! = BACKGROUND_MODE_INVALID ) {
oc - > mode = config - > mode ;
}
return false ;
}
}
// New config, just add it
wl_list_insert ( & state - > configs , & config - > link ) ;
return true ;
}
if ( argc < 4 | | argc > 5 ) {
sway_log ( SWAY_ERROR , " Do not run this program manually. "
" See `man 5 sway-output` and look for background options. " ) ;
return 1 ;
static void parse_command_line ( int argc , char * * argv ,
struct swaybg_state * state ) {
static struct option long_options [ ] = {
{ " color " , required_argument , NULL , ' c ' } ,
{ " help " , no_argument , NULL , ' h ' } ,
{ " image " , required_argument , NULL , ' i ' } ,
{ " mode " , required_argument , NULL , ' m ' } ,
{ " output " , required_argument , NULL , ' o ' } ,
{ " version " , no_argument , NULL , ' v ' } ,
{ 0 , 0 , 0 , 0 }
} ;
const char * usage =
" Usage: swaybg <options...> \n "
" \n "
" -c, --color Set the background color. \n "
" -h, --help Show help message and quit. \n "
" -i, --image Set the image to display. \n "
" -m, --mode Set the mode to use for the image. \n "
" -o, --output Set the output to operate on or * for all. \n "
" -v, --version Show the version number and quit. \n "
" \n "
" Background Modes: \n "
" stretch, fit, fill, center, tile, or solid_color \n " ;
struct swaybg_output_config * config = NULL ;
int c ;
while ( 1 ) {
int option_index = 0 ;
c = getopt_long ( argc , argv , " c:hi:m:o:v " , long_options , & option_index ) ;
if ( c = = - 1 ) {
break ;
}
switch ( c ) {
case ' c ' : // color
if ( ! config ) {
goto no_output ;
}
if ( ! is_valid_color ( optarg ) ) {
sway_log ( SWAY_ERROR , " Invalid color: %s " , optarg ) ;
continue ;
}
config - > color = parse_color ( optarg ) ;
break ;
case ' i ' : // image
if ( ! config ) {
goto no_output ;
}
free ( config - > image ) ;
config - > image = load_background_image ( optarg ) ;
if ( ! config - > image ) {
sway_log ( SWAY_ERROR , " Failed to load image: %s " , optarg ) ;
}
break ;
case ' m ' : // mode
if ( ! config ) {
goto no_output ;
}
config - > mode = parse_background_mode ( optarg ) ;
if ( config - > mode = = BACKGROUND_MODE_INVALID ) {
sway_log ( SWAY_ERROR , " Invalid mode: %s " , optarg ) ;
}
break ;
case ' o ' : // output
if ( config & & ! store_swaybg_output_config ( state , config ) ) {
// Empty config or merged on top of an existing one
destroy_swaybg_output_config ( config ) ;
}
config = calloc ( sizeof ( struct swaybg_output_config ) , 1 ) ;
config - > output = strdup ( optarg ) ;
config - > mode = BACKGROUND_MODE_INVALID ;
wl_list_init ( & config - > link ) ; // init for safe removal
break ;
case ' v ' : // version
fprintf ( stdout , " swaybg version " SWAY_VERSION " \n " ) ;
exit ( EXIT_SUCCESS ) ;
break ;
default :
fprintf ( c = = ' h ' ? stdout : stderr , " %s " , usage ) ;
exit ( c = = ' h ' ? EXIT_SUCCESS : EXIT_FAILURE ) ;
}
}
if ( config & & ! store_swaybg_output_config ( state , config ) ) {
// Empty config or merged on top of an existing one
destroy_swaybg_output_config ( config ) ;
}
args . output = argv [ 1 ] ;
args . path = argv [ 2 ] ;
// Check for invalid options
if ( optind < argc ) {
config = NULL ;
struct swaybg_output_config * tmp = NULL ;
wl_list_for_each_safe ( config , tmp , & state - > configs , link ) {
destroy_swaybg_output_config ( config ) ;
}
// continue into empty list
}
if ( wl_list_empty ( & state - > configs ) ) {
fprintf ( stderr , " %s " , usage ) ;
exit ( EXIT_FAILURE ) ;
}
args . mode = parse_background_mode ( argv [ 3 ] ) ;
if ( args . mode = = BACKGROUND_MODE_INVALID ) {
return 1 ;
// Set default mode and remove empties
config = NULL ;
struct swaybg_output_config * tmp = NULL ;
wl_list_for_each_safe ( config , tmp , & state - > configs , link ) {
if ( ! config - > image & & ! config - > color ) {
destroy_swaybg_output_config ( config ) ;
} else if ( config - > mode = = BACKGROUND_MODE_INVALID ) {
config - > mode = config - > image
? BACKGROUND_MODE_STRETCH
: BACKGROUND_MODE_SOLID_COLOR ;
}
}
return ;
no_output :
fprintf ( stderr , " Cannot operate on NULL output config \n " ) ;
exit ( EXIT_FAILURE ) ;
}
args . fallback = argc = = 5 ? argv [ 4 ] : NULL ;
int main ( int argc , char * * argv ) {
sway_log_init ( SWAY_DEBUG , NULL ) ;
if ( ! prepare_context ( & state ) ) {
return 1 ;
}
struct swaybg_state state = { 0 } ;
wl_list_init ( & state . configs ) ;
wl_list_init ( & state . outputs ) ;
parse_command_line ( argc , argv , & state ) ;
state . display = wl_display_connect ( NULL ) ;
if ( ! state . display ) {
@ -303,42 +522,21 @@ int main(int argc, const char **argv) {
zxdg_output_v1_add_listener ( output - > xdg_output ,
& xdg_output_listener , output ) ;
}
// Second roundtrip to get xdg_output properties
wl_display_roundtrip ( state . display ) ;
if ( state . output = = NULL ) {
sway_log ( SWAY_ERROR , " Cannot find output '%s' " , args . output ) ;
return 1 ;
}
state . surface = wl_compositor_create_surface ( state . compositor ) ;
assert ( state . surface ) ;
// Empty input region
state . input_region = wl_compositor_create_region ( state . compositor ) ;
assert ( state . input_region ) ;
wl_surface_set_input_region ( state . surface , state . input_region ) ;
state . layer_surface = zwlr_layer_shell_v1_get_layer_surface (
state . layer_shell , state . surface , state . output - > wl_output ,
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND , " wallpaper " ) ;
assert ( state . layer_surface ) ;
zwlr_layer_surface_v1_set_size ( state . layer_surface , 0 , 0 ) ;
zwlr_layer_surface_v1_set_anchor ( state . layer_surface ,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ) ;
zwlr_layer_surface_v1_set_exclusive_zone ( state . layer_surface , - 1 ) ;
zwlr_layer_surface_v1_add_listener ( state . layer_surface ,
& layer_surface_listener , & state ) ;
wl_surface_commit ( state . surface ) ;
wl_display_roundtrip ( state . display ) ;
state . run_display = true ;
while ( wl_display_dispatch ( state . display ) ! = - 1 & & state . run_display ) {
// This space intentionally left blank
}
struct swaybg_output * tmp_output ;
wl_list_for_each_safe ( output , tmp_output , & state . outputs , link ) {
destroy_swaybg_output ( output ) ;
}
struct swaybg_output_config * config = NULL , * tmp_config = NULL ;
wl_list_for_each_safe ( config , tmp_config , & state . configs , link ) {
destroy_swaybg_output_config ( config ) ;
}
return 0 ;
}