diff options
Diffstat (limited to 'panels/display')
27 files changed, 9283 insertions, 0 deletions
diff --git a/panels/display/TODO b/panels/display/TODO new file mode 100644 index 0000000..f09425c --- /dev/null +++ b/panels/display/TODO @@ -0,0 +1,837 @@ +Highlevel overview: + +Tablet rotation things +only when there is a tablet attached. + +Here is the OS X Display menu: + + Detect Displays + Turn on mirroring + -------------------------- + SyncMaster + - 1280 x 1024, 60 Hz, Millions + - 1344 x ... + -------------------------------- + Color LCD + - 1024 x 1024 ... + -------------------------- + Displays Preferences + + Color LCD means "laptop panel". + +- GTK+ work. + + Allow applications to be notified whenever monitors are added + or removed. Allow applications to get more detailed + information about the connected monitors. + + The main complication is that XRRGetScreenResources() is very + slow. We could call it only when the X server sends an event, + but it's not desirable to have every application freeze for + half a second. And certainly not desirable to have the X + server block for n * 0.5 seconds. + + With the X server work below we should be fine just calling + XRRGetScreenResources on startup and in response to events. + +- X server work: + + X server needs to poll for whether a monitor is plugged + in. Whenever it detects a change, it should do an EDID query, + and cache the resulting information. That way XRRGetScreenResources() + can be the speed of a normal roundtrip. It's desirable that + normal client requests can still be processed during the EDID + querying, but only a nice-to-have. + + Drivers need to work reliably. There could be substantial work + here. For F9, possibly only the Intel driver can be made to + work. + + Interrupts and events must be generated whenever something changes + about the outputs, if necessary by polling. + + Events must be emitted whenever something changes, including when + the reason for the change is a manual change. + + The maximum framebuffer must be dynamically changable. + +- Control panel work: + Capplet needs to be written. The main complications: + + - It needs to pay attention to events from the X server + and update itself, ie., add show new monitors if they become + available when the applet is shown. + + - It needs to store information under a key computed + from a monitor identifier. The complication here is that + it's not completely clear how to do this in GConf. + + - Would probably be worthwhile to drop libgnome/libgnomeui from + the craplets. + +- Metacity work: + - Metacity is already Xinerama aware, but it needs to update itself + when monitors come and go. + +- GNOME panel work: + - Is already Xinerama aware, but needs to listen and update itself + when monitors change. + +- Evince work: + - Make sure it deals sensibly with multiple monitors + +- OpenOffice work: + - Make sure it deals sensibly with multiple monitors + +- An Xlib call to just return all the available information would be + useful. At the moment we have to do a bunch of roundtrips to + get the information. This is a would-be-nice though. + +- A dbus service could be written that pops up the applet whenever a + monitor. It should only pop up if the new monitor is unknown. This + is at best a nice-to-have, and low priority in my opinion. + + +******************* Metacity + +Havoc: + +> I was just talking to bryan about this and "helping" him design it ;-) + +> But I wanted to be sure and lobby for a fix window managers +> need. Basically right now the WM can't tell "physical" from +> "logical" monitors. + +> A "logical" monitor is a desktop; it has its own panel, windows +> maximize to it, etc. + +> A "physical" monitor is a piece of hardware. + +> Sometimes people want to combine physical monitors into a video wall +> or just two monitors treated as one. Or at least a couple of noisy +> people in bugzilla want to do this. + +> When people talk about a "Xinerama aware" app or WM they usually +> mean that all physical monitors are treated as logical monitors, +> while lack of Xinerama-aware means treating the entire X screen (all +> physical monitors) as one logical monitor. + +> The problem is that the setting for "ignore Xinerama" or "don't be +> Xinerama aware" should be global to the desktop (GTK, all apps, WM) +> and should not be a window manager setting. + +> Bryan thought people who wanted non-Xinerama-aware should just use +> fvwm, which may be right, but what I'd say is that if there is any +> setting for this, it should be desktop-global and in this monitor +> config dialog. + +> It should not be a metacity or Compiz option, but in some way an X +> option in short. The implementation could be either an X server +> feature or an EWMH hint or whatever, but it should be controlled by +> the monitor config dialog and used by apps, GTK, etc. in addition to +> used by the WM. + +> People tend to insist this should be a WM option, but that's just +> busted, since GTK and apps also have Xinerama-awareness features. + + +******************* EDID + +edid-decode enhancements: + +- Rejects years <= 0x0f for all versions, but this should only be done + for monitors claiming conformance to 1.4 (since 1.4 was released in + 2006). A monitor produced in 2005 should have 0x0f - it's the only + reasonable thing to do. + +- Uses 0x80 as the conformance mask for 1.4, should be 0 + +- Should read from stdin + +- Should parse xrandr -verbose output more robustly + +- Color depth computation is wrong. It uses the formula + + (edid[0x14] >> 3) + 2 + + The correct formula to use is + + (edid[0x14] & 0x70) >> 3 + 4 + +- + +-=-=-=- +Computing a display name from EDID information: + + vendor = lookup_vendor (code); + + if (dsc_product && !is_gobbledigook (dsc_product)) + { + if (vendor && !fuzzy_string_search (vendor, dsc_product)) + prepend (vendor); + } + else + { + if (vendor) + append (vendor); + else + append ("Unknown"); + } + + if (has size) + { + convert_to_inches() + + append (" %d\"", inches) + } + +(Does this internationalize at all)? + +We also need the ability to get laptop names. The laptop panel may report +a manufacturer that has nothing to do with the laptop manufacturer. + +Needed XRandr output properties: + +- Modes that the monitor supports, or enough information that the + client can go throught the list of modes for the relevant + CRTC/Outputs and filter those out that the monitor can't support. + +- The preferred mode, if any. Also useful if we could get a "strongly + preferred" indication if it's an LCD with a fixed resolution. + +- Sufficient information that a fairly specific identifier can be + computed. The algorithm the client should use is: + + 1 Have we seen exactly this monitor before? If yes, use + settings for that. + + 2 Have we seen a monitor with similar specs before? If yes, + use settings for that. (But don't save, unless the user + changes the settings). + + 3 Otherwise, use some reasonable default for the monitor and + save it. + + A setting should only be used if the CRTC/Output allows it. Ie,. if + a user has installed a new video card, then previously-used settings + may no longer apply, so this must be checked every time. + + (1) Implies that we really need a globally unique identifier for + monitors. (2) is useful in an enterprise setting, but not absolutely + critical, since (3) would still handle the majority of cases. + + There is a question here: Where are machine specific preferences + stored? Havoc mentions three possibilities here: + + http://mail.gnome.org/archives/gnomecc-list/2001-October/msg00023.html + + I'm not sure if any of them are implementable at this point. Also + (1) may mostly take care of the problem. + + + Usecases: + + 1. Fixed setup with some number of monitors. + - They should be set to the correct mode on login. + Note that this involves setting the right position in the + framebuffer too. + + What if someone swaps two monitors? Users are going to expect + that the images will switch position. + + 2. Laptop being moved between home and work + - Setups should be detected and the correct mode set, at least on + login, but ideally when you put the laptop into the docking + station. + + 3. Laptop gets projector plugged in. + + Note the same model monitor can be used in two different ways. Ie., + at home, it's being used at one resolution, at work the same type of + monitor is used at a different resolution. + + Simple solution: + + - The on-disk database is just a list of monitors. Each monitor has an + associated mode. This has these problems: + - If someone uses the same monitor model in two different ways. + - If someone swaps the monitors around + + Better solution + + - The on-disk database is a list of configurations, where a + configuration is a list of monitors and what outputs they are + connected to, and the position in the framebuffer. + + - Picking a default configuration is then a matter of selecting the + closest existing configuration from the database. + + - If the stored configuration is a subset of the existing, + then use that - then pick the best mode available for the + rest of the monitors + + - If the stored configuration is a superset of the existing, + then use the projection of the configuration onto the monitors. + + - Pick the configuration with the most overlap in monitors. + Although, if a configuration differs only in what outputs + they are connected to, then those outputs should probably + get their original modes set. + + - Or maybe simply: + + - If there is an exact match, use it, if not, pick a default. + + - Picking a new default must never change the mode of any existing + output. + +******************* Capplet + +Somehow the applet will find out that a new monitor is plugged in +(either through notification, or through a refresh button). When this +happens, this monitor is looked up in a database and if it is found, +some suitable mode is set. + +Restrictions on the modes: + +- Monitors that are already plugged in should not get their mode + changed just because a new monitor is plugged in. + +- If the exact configuration of monitors is known, and all the old + monitors have the same mode as the known configuration, then just use + the known configuration. Also do this, if the configuration is a + subset of something known. + +- Otherwise, if the configuration is a subset of a known configuration + where the only difference is that existing monitors have different + modes, then try and convert that mode to something we can know + about. Maybe configurations should be stored in terms of edges that + line up. + +- Otherwise, just pick some good default for the mode, probably based + on the EDID prferred mode if possible. By default cloning is + probably best. + +- How do virtual desktops interact with this? + + +g-s-d: + +- On startup + + - It reads the configuration file into memory + + capplet --configure + + - It gathers the existing configuration from randr + + - If the existing config is in the file, set that mode + +- On changes, including changes to the config file [this is crack] + + - Reread configuration file + + - Compare new configuration to database, if it is there, set the + mode as appropriate + + - If a monitor was added, pop up a bubble + + capplet --show-bubble + + capplet --set-mode + +capplet + +- On changes + + - Update GUI + +- When user changes something, + + - Write configuration to file + + - Signal gsd somehow + +Schemes: + - configuration file changes + - randr code will have to be shared between gcc and gsd + + - binary installed by gcc + - something will still have to listen for changes to pop + up the notification bubble. + +Structure of capplet: + +- There is a database on disk with monitors and their corresponding + settings. + +- On startup, this database is read into memory. When the user accepts + new settings, it is written back to disk. + +- When something changes about the settings + + - If new configuration is in the database, use that mode + + - Else, find all outputs that are now connected but weren't before, + and set a default mode for them. + + - If GUI is running, update graphics. + + + - Notification thing: + - if + + - if the new configuration is found in the database, use it + + and added if they are not already there. Initial settings are + 1 what the output is already doing, if anything + 2 based on an existing sufficiently similar monitor, if possible + 3 some reasonable default. + +- When the user changes settings in the GUI, the corresponding monitor + in the database is updated. + +- Whenever the GUI settings change, for all displayed monitors the + possible modes are recomputed. + +- Whenever a new monitor is selected in the GUI, it first gets all its + possible modes computed based on the selections on other + outputs. Then, if the possible modes include the existing choice of + resolution, that is selected. + + Actually, + + - initially, the settings are copied from the current settings + + - whenever a gui setting changes for a monitor, all the other + monitors get their list of choices set to whatever is possible + given the chocie for the current monitor. A 'desired mode' is + maintained, and the closest choice to that is displayed. Whenever + the user actively selects something, that becomes the desired mode + for that monitor. + +- Required + + - Generate all outputs that are newly connected + + foreach_newly_connected (Configuration *before, Configuration *after, + OutputFunc); + + - A way to generate the best mode for a connected output + + existing best_mode() can probably be used + + - Given a list of modes, pick the one closest to a given mode. + + (a possibility here is: pick an exact match, if that's + impossible, then pick the best one with the same + width/height, if that's impossible, then just pick the + best mode on the list). + + - For a configuation, fix the mode for a subset of the outputs, then + list the combinations for the rest of the outputs. + + An obvious possibility here is to simply list all possibilities, + then weed out those that don't work. Is this too expensive? + It might be. + +Structure of login time program: + +- The configuration database is read + +- The current hardware configuration is generated + +- If the current configuration is found in the database, that mode is set. + +- If it isn't found, then nothing changes. + + This could just be gnome-screen-resolution-capplet --reset + +******************* Things that need to be done to the xrandr.patch: + +=== + +XRRGetScreenResources() is a roundtrip and very slow (~0.5 s). GTK+ +needs to keep information up-to-date by tracking events rather than +calling this function. In fact we probably can't call it at all unless +its performance improves significantly. + +If EDID processing really has to be this slow, and we can't get +interrupts when monitors are plugged in, then we have a problem, +because we can't do anything this expensive once per second. + + +Detailed notes (but most of the patch should be rewritten): + + +=== FIXME in gdkscreen-x11.c in get_width_mm() + +/* monitor pixel width / screen pixel width * screen_physical width */ + + + +=== Check for 1.2 library + +The patch should check that the 1.2 version of the XRandR library is +available before using the functions. A possibility is to not use any +RandR unless 1.2 is available, another is to conditionalize the code. + +The most sane thing is probably to just require 1.2. + +On the other hand, installing a newer gtk+ on a system with older X is +probably not that unusual, so maybe it's better to do the full 1.0, +vs. 1.1 vs 1.2 check. + +For now it just requires 1.2. + +Actually, this might be fine because the only place where we make use +of a 1.1 library is in the _gdk_x11_screen_size_changed() function, +but there we have a fallback that just updates the variables in the +Screen struct itself. + +So, only defining HAVE_RANDR if we detect 1.2 should be ok. + +=== Monitor information available + +- Subpixel information. This should be set automatically for the fonts and + store under the name of the monitor. If the user changes the font + configuration, that change should also be stored under the monitor name. + +- When a monitor we don't know about is plugged in, a configuration should + be generated: + + - Screen size, computed based on the location of the screens + + - RGBA information + + - Whether the screen has a panel on it + + - If there is a conflict between stored information and EDID, + the stored information wins + + + +New API so far: + +(* monitors_changed) signal +gdk_screen_get_monitor_width_mm() +gdk_screen_get_monitor_height_mm() +gdk_screen_get_monitor_name() => Note this is the output (eg. "DVI-0") + +We should probably also have +get_manufacturer() +get_serial() +get_resolutions() + +etc. + +Should there be a GdkMonitor object that would correspond to an +output? Or maybe GdkOutput? + +screen_list_monitors() + + +*************************** Issues XRandR/Xserver + +- We need polling in the X server, whenever something changes, X must + recompute the information and cache it, then send an event. Note the + situation where the user disconnects and reconnects a monitor within + the polling interval. The event could missed in that case since the polling + cannot do a full EDID query. Difficult to see a way around this. + + Actually, DDC allows random access, so it should be possible to just + read theq vendor id and manufacturer codes. This can be done once a + second without a problem. The polling should be turned off in power + saving mode anyway. + + - Driver work: + + - Intel driver: + + - EDID information is not reported for VGA when the output is not + turned on (i945 laptop). + + - Screen size must be dynamically changable. (No xorg.conf changes + should be required). + + - Make use of ACPI information when possible. + + Adam has code on his freedesktop page. + + - i830 laptop can be put in a state where XRandr reports that no + outputs are connected to a CRTC, but the panel is on. + + - Plug in VGA + - xrandr --auto + - xrandr --output VGA --off + - run chk + - xrandr --verbose will now not report any outputs as turned on + - run chk again - all screens will be turned off + + - Small Sun monitor - an 1152x921 mode is generated, but the + monitor doesn't handle that. The monitor itself only claims to + handle 1152x920. It doesn't look to me like there is anything + in the EDID information that would indicate that it could handle + 1152x921. + + This happens with a radeon as wellso it may be a bug in the + generic X server EDID parsing. The X server apparently + interpretes the standard timing 1152x920 as 1152x921. + + This happens because the X server uses + + hsize * 4 / 5 + + which gives 921 for 1152. By using + + (hsize / 5) * 4 + + you get 920. The 66 Hz version can bet set, the 76 Hz mode gets + sync out of range. (Would be interesting to find out whether the + 1152x920 ModeLine would allow the 76 Hz version to be set). + + This is for the ATI driver as shipped in F8: + + - XRRGetScreenResources() takes half a second. + + - Adam has now removed a workaround that caused some of the slowdown. + + - If a DVI monitor is disconnected, you get "Unknown" for connection + status. + + - If a VGA monitor is plugged in, then EDID information is not + available, even after running xrandr --verbose. The monitor has + to be plugged in at driver startup time, apparently. + + - Logging out and logging back in often results in some random mode being + set. We need mode selection to not be completely screwed up. + Currently it is. + + - The set up at server startup needs to be fixed. *If* randr actually works, + then we might be able to do something sensible. + + - We need to revisit the idea that many monitors have broken EDID data. + This may be less widespread than previously believed. + +- It may be useful to return the connector names as identifiers instead + of relying on UTF-8 strings. Ie., have an enum + + { UNKNOWN, OTHER, DVI, VGA, HDMI, ..., } + + in addition to the string. The difference between UNKNOWN and OTHER is that + UNKNOWN means the driver doesn't know, whereas OTHER means it is something + not listed in the enum (which could be listed in a later version). + +- Mouse cursor should be confined to the visible area. (It is already, I think) + +- It looks like EDID information is only available for one output + even though it is actually read according to the log file. + (nv, intel drivers) + + +********************************* + + + +DONE: + +Server work: + + - i830 laptop incorrectly reports BadMatch when you configure the + CRTC to drive both VGA and LVDS with the 1024x768 mode that both + outputs can handle. (It should return 'failed' if it can't do + that). Same for i945 laptop. It seems as if the same CRTC can't + drive more than one output at the same time on Intel. + + This was a client bug, but the documentation for SetCrtcConfig + should say that BadMatch will be returned if the outputs aren't + clones. + +GTK+ patch is in now. + +=== Add helper function + + ++ if (screen_x11->randr12) ++ { ++ XRRScreenResources *sr; ++ XRROutputInfo *output; ++ gchar *retval; ++ ++ sr = XRRGetScreenResources ( screen_x11->xdisplay, ++ screen_x11->xroot_window ); ++ ++ output = XRRGetOutputInfo ( screen_x11->xdisplay, ++ sr, ++ (RROutput)screen_x11->act_outputs[monitor_num] +); + + Might be worthwhile to factor this out into a + gdk_screen_get_output_info (screen, monitor_num) + helper function ? + +Instead of cutting and pasting all over creation + +* Calling XRRGetScreenResources all the time is not going to fly. It + takes hundreds of milliseconds ... Even if it didn't, it wouldn't + be acceptable to do all those roundtrips. + + +=== Some g_prints left + + +=== Version check + +Should be (maj > 1) || (maj == 1 && min >= 2) + + +=== Grep for TODO + + +=== Setup XRRSelectInput() + + You should call XRRSelectInput() at the same place where you are + calling XSelectInput() right now. The right place to handle the + XRandr events is the huge switch in gdkevents-x11.c:gdk_event_translate + Check out how other extension events are handled there, like + XKB, or XFixes. + + +=== Lots of variable naming issues, such as act_output and noutput + +=== Needs to select the input, and hook it up to the signal + +=== Add version markers to API + +=== API to turn monitors on and off? + +- DPMS not exposed through randr, maybe should be + + - DPMS is presumably a property of either an + output or a CRTC. Logically it's an output. + +- Need events when DPMS happens. Exposing the "screen saving on" on + dbus may not be good enough. + +=== Why does init_multihead_support() start by freeing monitors and +outputs? + +=== Do we disable Xinerama support entirely when 1.2 is in use? + +=== We should expose information about what parts of the screen monitors +are viewing. + +=== Make use of the EDID information? + + +-- details for X server -- + +In nv driver SorSetOutputProperty should return TRUE for unknown +properties. (Like the Intel driver does). + +Detecting plugged in + +- Periodically poll + - + + - One ddc probe takes 5 ms, according to a comment in the intel + driver. Running this twice a second would mean spending 1% of + overall time doing ddc polling, which is almost certainly not + acceptable. + + 1) Async I2C: + + void I2CProbeAsync(..., callback, data); + Bool I2CPending() + void I2CUpdate() + + In Dispatch, call I2CUpdate() + Before going idle, do + + while (I2CPending()) + I2CUpdate() + + Would need + RegisterDispatchFunction() (Is this called Wakeup?) + RegisterIdleFunction() + + Note the idle function should have the option of saying: + "check if something else happened; if not, call me again" and + "ok, I'm done - go idle". Otherwise, we would be blocking for + 5 ms whenever the X server went idle. So actually the idle + function should be + + if (I2CPending()) + { + I2CUpdate(); + return TRUE; /* call me again */ + } + else + { + return FALSE; /* I'm done */ + } + + What happens if another I2C requests come in while an async one + is pending? Most likely we simply finish whatever is going on, + then process the new request. + + What happens if an X request takes so long that we get timeouts on + the i2c bus? Good question. Need to read the VESA ddc spec. + + 2) Run the polling in a separate thread. + + Probably crack. + + 3) Run the polling less, maybe once every three seconds. + +-- details for control panel -- +Screen changes + - Currently it is polling via rw_screen_refresh(), which will always emit + a screen-changed event. In reponse to this event the capplet currently + checks whether anything changed physically about the setup. This means + the capplet can't react to external changes to modes. On the other hand + if it didn't +Disallow combinations that would exceed the screen ranges. + - Note rotations + +Give rw objects stable positions in memory so that they can be cached +across screen_changed events. + +Add Clone Mode + +Drag and drop for the monitors + - 2 dimensional layout + +Store make and model in monitors.xml, then if serial numbers don't +match, fall back to a make and model match. Users with an nfs mounted +home directory should not have to reconfigure for each new system they +log in to. + +Make sure text is scaled correctly + +Need to sanitize naming + RWOutput vs Output - should probably be OutputInfo + rate vs. freq - decide on one + +Should probably reconsider the use of null terminated arrays. +Maybe lists would be better. + +Pick a fixed scale, so that two 1024x768 don't look like two 6x4. + - An alternative would be to draw a checkerboard pattern + below the monitors. + + + +done: + +Add rotation + +Disable panel checkbox for now + +Patch into gnome-desktop + +Find out how to share code between gcc and gsd + +Make it assign coordinates correctly + - including computing correct screen size + diff --git a/panels/display/cc-display-arrangement.c b/panels/display/cc-display-arrangement.c new file mode 100644 index 0000000..ae3541e --- /dev/null +++ b/panels/display/cc-display-arrangement.c @@ -0,0 +1,978 @@ +/* cc-display-arrangement.c + * + * Copyright (C) 2007, 2008, 2017 Red Hat, Inc. + * Copyright (C) 2013 Intel, Inc. + * + * Written by: Benjamin Berg <bberg@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include "cc-display-arrangement.h" +#include "cc-display-config.h" + +struct _CcDisplayArrangement +{ + GtkDrawingArea object; + + CcDisplayConfig *config; + + cairo_matrix_t to_widget; + cairo_matrix_t to_actual; + + gboolean drag_active; + CcDisplayMonitor *selected_output; + CcDisplayMonitor *prelit_output; + /* Starting position of cursor inside the monitor. */ + gdouble drag_anchor_x; + gdouble drag_anchor_y; + + guint major_snap_distance; +}; + +typedef struct _CcDisplayArrangement CcDisplayArrangement; + +enum { + PROP_0, + PROP_CONFIG, + PROP_SELECTED_OUTPUT, + PROP_LAST +}; + +typedef enum { + SNAP_DIR_NONE = 0, + SNAP_DIR_X = 1 << 0, + SNAP_DIR_Y = 1 << 1, + SNAP_DIR_BOTH = (SNAP_DIR_X | SNAP_DIR_Y), +} SnapDirection; + +typedef struct { + cairo_matrix_t to_widget; + guint major_snap_distance; + gdouble dist_x; + gdouble dist_y; + gint mon_x; + gint mon_y; + SnapDirection snapped; +} SnapData; + +#define MARGIN_PX 0 +#define MARGIN_MON 0.66 +#define MAJOR_SNAP_DISTANCE 25 +#define MINOR_SNAP_DISTANCE 5 +#define MIN_OVERLAP 25 + +G_DEFINE_TYPE (CcDisplayArrangement, cc_display_arrangement, GTK_TYPE_DRAWING_AREA) + +static GParamSpec *props[PROP_LAST]; + +static void +apply_rotation_to_geometry (CcDisplayMonitor *output, + int *w, + int *h) +{ + CcDisplayRotation rotation; + + rotation = cc_display_monitor_get_rotation (output); + if ((rotation == CC_DISPLAY_ROTATION_90) || (rotation == CC_DISPLAY_ROTATION_270)) + { + int tmp; + tmp = *h; + *h = *w; + *w = tmp; + } +} + +/* get_geometry */ +static void +get_scaled_geometry (CcDisplayConfig *config, + CcDisplayMonitor *output, + int *x, + int *y, + int *w, + int *h) +{ + if (cc_display_monitor_is_active (output)) + { + cc_display_monitor_get_geometry (output, x, y, w, h); + } + else + { + cc_display_monitor_get_geometry (output, x, y, NULL, NULL); + cc_display_mode_get_resolution (cc_display_monitor_get_preferred_mode (output), w, h); + } + + if (cc_display_config_is_layout_logical (config)) + { + double scale = cc_display_monitor_get_scale (output); + *w = round (*w / scale); + *h = round (*h / scale); + } + + apply_rotation_to_geometry (output, w, h); +} + +static void +get_bounding_box (CcDisplayConfig *config, + gint *x1, + gint *y1, + gint *x2, + gint *y2, + gint *max_w, + gint *max_h) +{ + GList *outputs, *l; + + g_assert (x1 && y1 && x2 && y2); + + *x1 = *y1 = G_MAXINT; + *x2 = *y2 = G_MININT; + *max_w = 0; + *max_h = 0; + + outputs = cc_display_config_get_monitors (config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + int x, y, w, h; + + if (!cc_display_monitor_is_useful (output)) + continue; + + get_scaled_geometry (config, output, &x, &y, &w, &h); + + *x1 = MIN (*x1, x); + *y1 = MIN (*y1, y); + *x2 = MAX (*x2, x + w); + *y2 = MAX (*y2, y + h); + *max_w = MAX (*max_w, w); + *max_h = MAX (*max_h, h); + } +} + +static void +monitor_get_drawing_rect (CcDisplayArrangement *self, + CcDisplayMonitor *output, + gint *x1, + gint *y1, + gint *x2, + gint *y2) +{ + gdouble x, y; + + get_scaled_geometry (self->config, output, x1, y1, x2, y2); + + /* get_scaled_geometry returns the width and height */ + *x2 = *x1 + *x2; + *y2 = *y1 + *y2; + + x = *x1; y = *y1; + cairo_matrix_transform_point (&self->to_widget, &x, &y); + *x1 = round (x); + *y1 = round (y); + + x = *x2; y = *y2; + cairo_matrix_transform_point (&self->to_widget, &x, &y); + *x2 = round (x); + *y2 = round (y); +} + + +static void +get_snap_distance (SnapData *snap_data, + gint mon_x, + gint mon_y, + gint new_x, + gint new_y, + gdouble *dist_x, + gdouble *dist_y) +{ + gdouble local_dist_x, local_dist_y; + + local_dist_x = ABS (mon_x - new_x); + local_dist_y = ABS (mon_y - new_y); + + cairo_matrix_transform_distance (&snap_data->to_widget, &local_dist_x, &local_dist_y); + + if (dist_x) + *dist_x = local_dist_x; + if (dist_y) + *dist_y = local_dist_y; +} + +static void +maybe_update_snap (SnapData *snap_data, + gint mon_x, + gint mon_y, + gint new_x, + gint new_y, + SnapDirection snapped, + SnapDirection major_axis, + gint minor_unlimited) +{ + SnapDirection update_snap = SNAP_DIR_NONE; + gdouble dist_x, dist_y; + gdouble dist; + + get_snap_distance (snap_data, mon_x, mon_y, new_x, new_y, &dist_x, &dist_y); + dist = MAX (dist_x, dist_y); + + /* Snap by the variable max snap distance on the major axis, ensure the + * minor axis is below the minimum snapping distance (often just zero). */ + switch (major_axis) + { + case SNAP_DIR_X: + if (dist_x > snap_data->major_snap_distance) + return; + if (dist_y > MINOR_SNAP_DISTANCE) + { + if (new_y > mon_y && minor_unlimited <= 0) + return; + if (new_y < mon_y && minor_unlimited >= 0) + return; + } + break; + + case SNAP_DIR_Y: + if (dist_y > snap_data->major_snap_distance) + return; + if (dist_x > MINOR_SNAP_DISTANCE) + { + if (new_x > mon_x && minor_unlimited <= 0) + return; + if (new_x < mon_x && minor_unlimited >= 0) + return; + } + break; + + default: + g_assert_not_reached(); + } + + if (snapped == SNAP_DIR_BOTH) + { + if (snap_data->snapped == SNAP_DIR_NONE) + update_snap = SNAP_DIR_BOTH; + + /* Update, if this is closer on the main axis. */ + if (((major_axis == SNAP_DIR_X) && (dist_x < snap_data->dist_x)) || + ((major_axis == SNAP_DIR_Y) && (dist_y < snap_data->dist_y))) + { + update_snap = SNAP_DIR_BOTH; + } + + /* Also update if we were only snapping in one direction earlier and it + * is better or equally good. */ + if ((snap_data->snapped == SNAP_DIR_X && (dist <= snap_data->dist_x)) || + (snap_data->snapped == SNAP_DIR_Y && (dist <= snap_data->dist_y))) + { + update_snap = SNAP_DIR_BOTH; + } + + /* Also allow a minor axis to be added if the first axis remains identical. */ + if (((snap_data->snapped == SNAP_DIR_X) && (major_axis == SNAP_DIR_X) && (new_x == snap_data->mon_x)) || + ((snap_data->snapped == SNAP_DIR_Y) && (major_axis == SNAP_DIR_Y) && (new_y == snap_data->mon_y))) + { + update_snap = SNAP_DIR_BOTH; + } + } + else if (snapped == SNAP_DIR_X) + { + if (dist_x < snap_data->dist_x || (snap_data->snapped & SNAP_DIR_X) == SNAP_DIR_NONE) + update_snap = SNAP_DIR_X; + } + else if (snapped == SNAP_DIR_Y) + { + if (dist_y < snap_data->dist_y || (snap_data->snapped & SNAP_DIR_Y) == SNAP_DIR_NONE) + update_snap = SNAP_DIR_Y; + } + else + { + g_assert_not_reached (); + } + + if (update_snap & SNAP_DIR_X) + { + snap_data->dist_x = dist_x; + snap_data->mon_x = new_x; + snap_data->snapped = snap_data->snapped | SNAP_DIR_X; + } + if (update_snap & SNAP_DIR_Y) + { + snap_data->dist_y = dist_y; + snap_data->mon_y = new_y; + snap_data->snapped = snap_data->snapped | SNAP_DIR_Y; + } +} + +static void +find_best_snapping (CcDisplayConfig *config, + CcDisplayMonitor *snap_output, + SnapData *snap_data) +{ + GList *outputs, *l; + gint x1, y1, x2, y2; + gint w, h; + + g_assert (snap_data != NULL); + + get_scaled_geometry (config, snap_output, &x1, &y1, &w, &h); + x2 = x1 + w; + y2 = y1 + h; + +#define OVERLAP(_s1, _s2, _t1, _t2) ((_s1) <= (_t2) && (_t1) <= (_s2)) + + outputs = cc_display_config_get_monitors (config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + gint _x1, _y1, _x2, _y2, _h, _w; + gint bottom_snap_pos; + gint top_snap_pos; + gint left_snap_pos; + gint right_snap_pos; + gdouble dist_x, dist_y; + gdouble tmp; + + if (output == snap_output) + continue; + + if (!cc_display_monitor_is_useful (output)) + continue; + + get_scaled_geometry (config, output, &_x1, &_y1, &_w, &_h); + _x2 = _x1 + _w; + _y2 = _y1 + _h; + + top_snap_pos = _y1 - h; + bottom_snap_pos = _y2; + left_snap_pos = _x1 - w; + right_snap_pos = _x2; + + dist_y = 9999; + /* overlap on the X axis */ + if (OVERLAP (x1, x2, _x1, _x2)) + { + get_snap_distance (snap_data, x1, y1, x1, top_snap_pos, NULL, &dist_y); + get_snap_distance (snap_data, x1, y1, x1, bottom_snap_pos, NULL, &tmp); + dist_y = MIN(dist_y, tmp); + } + + dist_x = 9999; + /* overlap on the Y axis */ + if (OVERLAP (y1, y2, _y1, _y2)) + { + get_snap_distance (snap_data, x1, y1, left_snap_pos, y1, &dist_x, NULL); + get_snap_distance (snap_data, x1, y1, right_snap_pos, y1, &tmp, NULL); + dist_x = MIN(dist_x, tmp); + } + + /* We only snap horizontally or vertically to an edge of the same monitor */ + if (dist_y < dist_x) + { + maybe_update_snap (snap_data, x1, y1, x1, top_snap_pos, SNAP_DIR_Y, SNAP_DIR_Y, 0); + maybe_update_snap (snap_data, x1, y1, x1, bottom_snap_pos, SNAP_DIR_Y, SNAP_DIR_Y, 0); + } + else if (dist_x < 9999) + { + maybe_update_snap (snap_data, x1, y1, left_snap_pos, y1, SNAP_DIR_X, SNAP_DIR_X, 0); + maybe_update_snap (snap_data, x1, y1, right_snap_pos, y1, SNAP_DIR_X, SNAP_DIR_X, 0); + } + + /* Left/right edge identical on the top */ + maybe_update_snap (snap_data, x1, y1, _x1, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0); + maybe_update_snap (snap_data, x1, y1, _x2 - w, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0); + + /* Left/right edge identical on the bottom */ + maybe_update_snap (snap_data, x1, y1, _x1, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0); + maybe_update_snap (snap_data, x1, y1, _x2 - w, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 0); + + /* Top/bottom edge identical on the left */ + maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y1, SNAP_DIR_BOTH, SNAP_DIR_X, 0); + maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y2 - h, SNAP_DIR_BOTH, SNAP_DIR_X, 0); + + /* Top/bottom edge identical on the right */ + maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y1, SNAP_DIR_BOTH, SNAP_DIR_X, 0); + maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y2 - h, SNAP_DIR_BOTH, SNAP_DIR_X, 0); + + /* If snapping is infinite, then add snapping points with minimal overlap + * to prevent detachment. + * This is similar to the above but simply re-defines the snapping pos + * to have only minimal overlap */ + if (snap_data->major_snap_distance == G_MAXUINT) + { + /* Hanging over the left/right edge on the top */ + maybe_update_snap (snap_data, x1, y1, _x1 - w + MIN_OVERLAP, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 1); + maybe_update_snap (snap_data, x1, y1, _x2 - MIN_OVERLAP, top_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, -1); + + /* Left/right edge identical on the bottom */ + maybe_update_snap (snap_data, x1, y1, _x1 - w + MIN_OVERLAP, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, 1); + maybe_update_snap (snap_data, x1, y1, _x2 - MIN_OVERLAP, bottom_snap_pos, SNAP_DIR_BOTH, SNAP_DIR_Y, -1); + + /* Top/bottom edge identical on the left */ + maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y1 - h + MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, 1); + maybe_update_snap (snap_data, x1, y1, left_snap_pos, _y2 - MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, -1); + + /* Top/bottom edge identical on the right */ + maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y1 - h + MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, 1); + maybe_update_snap (snap_data, x1, y1, right_snap_pos, _y2 - MIN_OVERLAP, SNAP_DIR_BOTH, SNAP_DIR_X, -1); + } + } + +#undef OVERLAP +} + +static void +cc_display_arrangement_update_matrices (CcDisplayArrangement *self) +{ + GtkAllocation allocation; + gdouble scale, scale_h, scale_w; + gint x1, y1, x2, y2, max_w, max_h; + + g_assert (self->config); + + /* Do not update the matrices while the user is dragging things around. */ + if (self->drag_active) + return; + + get_bounding_box (self->config, &x1, &y1, &x2, &y2, &max_w, &max_h); + gtk_widget_get_allocation (GTK_WIDGET (self), &allocation); + + scale_h = (gdouble) (allocation.width - 2 * MARGIN_PX) / (x2 - x1 + max_w * 2 * MARGIN_MON); + scale_w = (gdouble) (allocation.height - 2 * MARGIN_PX) / (y2 - y1 + max_h * 2 * MARGIN_MON); + + scale = MIN (scale_h, scale_w); + + cairo_matrix_init_identity (&self->to_widget); + cairo_matrix_translate (&self->to_widget, allocation.width / 2.0, allocation.height / 2.0); + cairo_matrix_scale (&self->to_widget, scale, scale); + cairo_matrix_translate (&self->to_widget, - (x1 + x2) / 2.0, - (y1 + y2) / 2.0); + + self->to_actual = self->to_widget; + cairo_matrix_invert (&self->to_actual); +} + +static CcDisplayMonitor* +cc_display_arrangement_find_monitor_at (CcDisplayArrangement *self, + gint x, + gint y) +{ + g_autoptr(GList) outputs = NULL; + GList *l; + + outputs = g_list_copy (cc_display_config_get_monitors (self->config)); + + if (self->selected_output) + outputs = g_list_prepend (outputs, self->selected_output); + + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + gint x1, y1, x2, y2; + + if (!cc_display_monitor_is_useful (output)) + continue; + + monitor_get_drawing_rect (self, output, &x1, &y1, &x2, &y2); + + if (x >= x1 && x <= x2 && y >= y1 && y <= y2) + return output; + } + + return NULL; +} + +static void +on_output_changed_cb (CcDisplayArrangement *self, + CcDisplayMonitor *output) +{ + if (cc_display_config_count_useful_monitors (self->config) > 2) + self->major_snap_distance = MAJOR_SNAP_DISTANCE; + else + self->major_snap_distance = G_MAXUINT; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +cc_display_arrangement_draw (GtkDrawingArea *drawing_area, + cairo_t *cr, + gint width, + gint height, + gpointer user_data) +{ + CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (drawing_area); + GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self)); + g_autoptr(GList) outputs = NULL; + GList *l; + + if (!self->config) + return; + + cc_display_arrangement_update_matrices (self); + + /* Draw in reverse order so that hit detection matches visual. Also pull + * the selected output to the back. */ + outputs = g_list_copy (cc_display_config_get_monitors (self->config)); + outputs = g_list_remove (outputs, self->selected_output); + if (self->selected_output != NULL) + outputs = g_list_prepend (outputs, self->selected_output); + outputs = g_list_reverse (outputs); + + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + GtkStateFlags state = GTK_STATE_FLAG_NORMAL; + GtkBorder border, padding, margin; + gint x1, y1, x2, y2; + gint w, h; + gint num; + + if (!cc_display_monitor_is_useful (output)) + continue; + + gtk_style_context_save (context); + cairo_save (cr); + + gtk_style_context_add_class (context, "monitor"); + + if (output == self->selected_output) + state |= GTK_STATE_FLAG_SELECTED; + if (output == self->prelit_output) + state |= GTK_STATE_FLAG_PRELIGHT; + + gtk_style_context_set_state (context, state); + if (cc_display_monitor_is_primary (output) || cc_display_config_is_cloning (self->config)) + gtk_style_context_add_class (context, "primary"); + + /* Set in cc-display-panel.c */ + num = cc_display_monitor_get_ui_number (output); + + monitor_get_drawing_rect (self, output, &x1, &y1, &x2, &y2); + w = x2 - x1; + h = y2 - y1; + + cairo_translate (cr, x1, y1); + + gtk_style_context_get_margin (context, &margin); + + cairo_translate (cr, margin.left, margin.top); + + w -= margin.left + margin.right; + h -= margin.top + margin.bottom; + + gtk_render_background (context, cr, 0, 0, w, h); + gtk_render_frame (context, cr, 0, 0, w, h); + + gtk_style_context_get_border (context, &border); + gtk_style_context_get_padding (context, &padding); + + w -= border.left + border.right + padding.left + padding.right; + h -= border.top + border.bottom + padding.top + padding.bottom; + + cairo_translate (cr, border.left + padding.left, border.top + padding.top); + + if (num > 0) + { + PangoLayout *layout; + g_autofree gchar *number_str = NULL; + PangoRectangle extents; + GdkRGBA color; + gdouble text_width, text_padding; + + gtk_style_context_add_class (context, "monitor-label"); + gtk_style_context_remove_class (context, "monitor"); + + gtk_style_context_get_border (context, &border); + gtk_style_context_get_padding (context, &padding); + + cairo_translate (cr, w / 2, h / 2); + + number_str = g_strdup_printf ("%d", num); + layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), number_str); + pango_layout_get_extents (layout, NULL, &extents); + + h = (extents.height - extents.y) / PANGO_SCALE; + text_width = (extents.width - extents.x) / PANGO_SCALE; + w = MAX (text_width, h - padding.left - padding.right); + text_padding = w - text_width; + + w += border.left + border.right + padding.left + padding.right; + h += border.top + border.bottom + padding.top + padding.bottom; + + /* Enforce evenness */ + if ((w % 2) != 0) + w++; + if ((h % 2) != 0) + h++; + + cairo_translate (cr, - w / 2, - h / 2); + + gtk_render_background (context, cr, 0, 0, w, h); + gtk_render_frame (context, cr, 0, 0, w, h); + + cairo_translate (cr, border.left + padding.left, border.top + padding.top); + cairo_translate (cr, extents.x + text_padding / 2, 0); + + gtk_style_context_get_color (context, &color); + gdk_cairo_set_source_rgba (cr, &color); + + gtk_render_layout (context, cr, 0, 0, layout); + g_object_unref (layout); + } + + gtk_style_context_restore (context); + cairo_restore (cr); + } +} + +static gboolean +on_click_gesture_pressed_cb (GtkGestureClick *click_gesture, + gint n_press, + gdouble x, + gdouble y, + CcDisplayArrangement *self) +{ + CcDisplayMonitor *output; + gdouble event_x, event_y; + gint mon_x, mon_y; + + if (!self->config) + return FALSE; + + g_return_val_if_fail (self->drag_active == FALSE, FALSE); + + output = cc_display_arrangement_find_monitor_at (self, x, y); + if (!output) + return FALSE; + + event_x = x; + event_y = y; + + cairo_matrix_transform_point (&self->to_actual, &event_x, &event_y); + cc_display_monitor_get_geometry (output, &mon_x, &mon_y, NULL, NULL); + + cc_display_arrangement_set_selected_output (self, output); + + if (cc_display_config_count_useful_monitors (self->config) > 1) + { + self->drag_active = TRUE; + self->drag_anchor_x = event_x - mon_x; + self->drag_anchor_y = event_y - mon_y; + } + + return TRUE; +} + +static gboolean +on_click_gesture_released_cb (GtkGestureClick *click_gesture, + gint n_press, + gdouble x, + gdouble y, + CcDisplayArrangement *self) +{ + CcDisplayMonitor *output; + + if (!self->config) + return FALSE; + + if (!self->drag_active) + return FALSE; + + self->drag_active = FALSE; + + output = cc_display_arrangement_find_monitor_at (self, x, y); + gtk_widget_set_cursor_from_name (GTK_WIDGET (self), + output != NULL ? "fleur" : NULL); + + /* And queue a redraw to recenter everything */ + gtk_widget_queue_draw (GTK_WIDGET (self)); + + g_signal_emit_by_name (G_OBJECT (self), "updated"); + + return TRUE; +} + +static gboolean +on_motion_controller_motion_cb (GtkEventControllerMotion *motion_controller, + gdouble x, + gdouble y, + CcDisplayArrangement *self) +{ + gdouble event_x, event_y; + gint mon_x, mon_y; + SnapData snap_data; + + if (!self->config) + return FALSE; + + if (cc_display_config_count_useful_monitors (self->config) <= 1) + return FALSE; + + if (!self->drag_active) + { + CcDisplayMonitor *output; + output = cc_display_arrangement_find_monitor_at (self, x, y); + + gtk_widget_set_cursor_from_name (GTK_WIDGET (self), + output != NULL ? "fleur" : NULL); + if (self->prelit_output != output) + gtk_widget_queue_draw (GTK_WIDGET (self)); + + self->prelit_output = output; + + return FALSE; + } + + g_assert (self->selected_output); + + event_x = x; + event_y = y; + + cairo_matrix_transform_point (&self->to_actual, &event_x, &event_y); + + mon_x = round (event_x - self->drag_anchor_x); + mon_y = round (event_y - self->drag_anchor_y); + + /* The monitor is now at the location as if there was no snapping whatsoever. */ + snap_data.snapped = SNAP_DIR_NONE; + snap_data.mon_x = mon_x; + snap_data.mon_y = mon_y; + snap_data.dist_x = 0; + snap_data.dist_y = 0; + snap_data.to_widget = self->to_widget; + snap_data.major_snap_distance = self->major_snap_distance; + + cc_display_monitor_set_position (self->selected_output, mon_x, mon_y); + + find_best_snapping (self->config, self->selected_output, &snap_data); + + cc_display_monitor_set_position (self->selected_output, snap_data.mon_x, snap_data.mon_y); + + return TRUE; +} + +static void +cc_display_arrangement_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (object); + + switch (prop_id) + { + case PROP_CONFIG: + g_value_set_object (value, self->config); + break; + + case PROP_SELECTED_OUTPUT: + g_value_set_object (value, self->selected_output); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_arrangement_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcDisplayArrangement *obj = CC_DISPLAY_ARRANGEMENT (object); + + switch (prop_id) + { + case PROP_CONFIG: + cc_display_arrangement_set_config (obj, g_value_get_object (value)); + break; + + case PROP_SELECTED_OUTPUT: + cc_display_arrangement_set_selected_output (obj, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_arrangement_finalize (GObject *object) +{ + CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (object); + + g_clear_object (&self->config); + + G_OBJECT_CLASS (cc_display_arrangement_parent_class)->finalize (object); +} + +static void +cc_display_arrangement_class_init (CcDisplayArrangementClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = cc_display_arrangement_finalize; + gobject_class->get_property = cc_display_arrangement_get_property; + gobject_class->set_property = cc_display_arrangement_set_property; + + props[PROP_CONFIG] = g_param_spec_object ("config", "Display Config", + "The display configuration to work with", + CC_TYPE_DISPLAY_CONFIG, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + props[PROP_SELECTED_OUTPUT] = g_param_spec_object ("selected-output", "Selected Output", + "The output that is currently selected on the configuration", + CC_TYPE_DISPLAY_MONITOR, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + PROP_LAST, + props); + + g_signal_new ("updated", + CC_TYPE_DISPLAY_ARRANGEMENT, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (klass), "display-arrangement"); +} + +static void +cc_display_arrangement_init (CcDisplayArrangement *self) +{ + GtkEventController *motion_controller; + GtkGesture *click_gesture; + + click_gesture = gtk_gesture_click_new (); + g_signal_connect (click_gesture, "pressed", G_CALLBACK (on_click_gesture_pressed_cb), self); + g_signal_connect (click_gesture, "released", G_CALLBACK (on_click_gesture_released_cb), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (click_gesture)); + + motion_controller = gtk_event_controller_motion_new (); + g_signal_connect (motion_controller, "motion", G_CALLBACK (on_motion_controller_motion_cb), self); + gtk_widget_add_controller (GTK_WIDGET (self), motion_controller); + + gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (self), + cc_display_arrangement_draw, + self, + NULL); + + self->major_snap_distance = MAJOR_SNAP_DISTANCE; +} + +CcDisplayArrangement* +cc_display_arrangement_new (CcDisplayConfig *config) +{ + return g_object_new (CC_TYPE_DISPLAY_ARRANGEMENT, "config", config, NULL); +} + +CcDisplayConfig* +cc_display_arrangement_get_config (CcDisplayArrangement *self) +{ + return self->config; +} + +void +cc_display_arrangement_set_config (CcDisplayArrangement *self, + CcDisplayConfig *config) +{ + const gchar *signals[] = { "rotation", "mode", "primary", "active", "scale", "position-changed", "is-usable" }; + GList *outputs, *l; + guint i; + + if (self->config) + { + outputs = cc_display_config_get_monitors (self->config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + g_signal_handlers_disconnect_by_data (output, self); + } + } + g_clear_object (&self->config); + + self->drag_active = FALSE; + + /* Listen to all the signals */ + if (config) + { + self->config = g_object_ref (config); + + outputs = cc_display_config_get_monitors (self->config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + for (i = 0; i < G_N_ELEMENTS (signals); ++i) + g_signal_connect_object (output, signals[i], G_CALLBACK (on_output_changed_cb), self, G_CONNECT_SWAPPED); + } + } + + cc_display_arrangement_set_selected_output (self, NULL); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); +} + +CcDisplayMonitor* +cc_display_arrangement_get_selected_output (CcDisplayArrangement *self) +{ + return self->selected_output; +} + +void +cc_display_arrangement_set_selected_output (CcDisplayArrangement *self, + CcDisplayMonitor *output) +{ + g_return_if_fail (self->drag_active == FALSE); + + /* XXX: Could check that it actually belongs to the right config object. */ + self->selected_output = output; + + gtk_widget_queue_draw (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_OUTPUT]); +} + +void +cc_display_config_snap_output (CcDisplayConfig *config, + CcDisplayMonitor *output) +{ + SnapData snap_data; + gint x, y, w, h; + + if (!cc_display_monitor_is_useful (output)) + return; + + if (cc_display_config_count_useful_monitors (config) <= 1) + return; + + get_scaled_geometry (config, output, &x, &y, &w, &h); + + snap_data.snapped = SNAP_DIR_NONE; + snap_data.mon_x = x; + snap_data.mon_y = y; + snap_data.dist_x = 0; + snap_data.dist_y = 0; + cairo_matrix_init_identity (&snap_data.to_widget); + snap_data.major_snap_distance = G_MAXUINT; + + find_best_snapping (config, output, &snap_data); + + cc_display_monitor_set_position (output, snap_data.mon_x, snap_data.mon_y); +} diff --git a/panels/display/cc-display-arrangement.h b/panels/display/cc-display-arrangement.h new file mode 100644 index 0000000..9494c48 --- /dev/null +++ b/panels/display/cc-display-arrangement.h @@ -0,0 +1,47 @@ +/* -*- mode: c; style: linux -*- + * + * Copyright (C) 2017 Red Hat, Inc. + * + * Written by: Benjamin Berg <bberg@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gtk/gtk.h> +#include "cc-display-config.h" + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_ARRANGEMENT cc_display_arrangement_get_type () +G_DECLARE_FINAL_TYPE (CcDisplayArrangement, cc_display_arrangement, CC, DISPLAY_ARRANGEMENT, GtkDrawingArea); + +CcDisplayArrangement* cc_display_arrangement_new (CcDisplayConfig *config); + +CcDisplayConfig* cc_display_arrangement_get_config (CcDisplayArrangement *self); +void cc_display_arrangement_set_config (CcDisplayArrangement *self, + CcDisplayConfig *config); + +CcDisplayMonitor* cc_display_arrangement_get_selected_output (CcDisplayArrangement *arr); +void cc_display_arrangement_set_selected_output (CcDisplayArrangement *arr, + CcDisplayMonitor *output); + +/* This is a bit of an odd-ball, but it currently makes sense to have it with + * the arrangement widget where the snapping code lives. */ +void cc_display_config_snap_output (CcDisplayConfig *config, + CcDisplayMonitor *output); + +G_END_DECLS + diff --git a/panels/display/cc-display-config-dbus.c b/panels/display/cc-display-config-dbus.c new file mode 100644 index 0000000..c3b269d --- /dev/null +++ b/panels/display/cc-display-config-dbus.c @@ -0,0 +1,2116 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <float.h> +#include <math.h> +#include <gio/gio.h> + +#include "cc-display-config-dbus.h" + +#define MODE_BASE_FORMAT "siiddad" +#define MODE_FORMAT "(" MODE_BASE_FORMAT "a{sv})" +#define MODES_FORMAT "a" MODE_FORMAT +#define MONITOR_SPEC_FORMAT "(ssss)" +#define MONITOR_FORMAT "(" MONITOR_SPEC_FORMAT MODES_FORMAT "a{sv})" +#define MONITORS_FORMAT "a" MONITOR_FORMAT + +#define LOGICAL_MONITOR_MONITORS_FORMAT "a" MONITOR_SPEC_FORMAT +#define LOGICAL_MONITOR_FORMAT "(iidub" LOGICAL_MONITOR_MONITORS_FORMAT "a{sv})" +#define LOGICAL_MONITORS_FORMAT "a" LOGICAL_MONITOR_FORMAT + +#define CURRENT_STATE_FORMAT "(u" MONITORS_FORMAT LOGICAL_MONITORS_FORMAT "a{sv})" + +typedef enum _CcDisplayModeFlags +{ + MODE_PREFERRED = 1 << 0, + MODE_CURRENT = 1 << 1, + MODE_INTERLACED = 1 << 2, +} CcDisplayModeFlags; + +struct _CcDisplayModeDBus +{ + CcDisplayMode parent_instance; + CcDisplayMonitorDBus *monitor; + + char *id; + int width; + int height; + double refresh_rate; + double preferred_scale; + GArray *supported_scales; + guint32 flags; +}; + +G_DEFINE_TYPE (CcDisplayModeDBus, + cc_display_mode_dbus, + CC_TYPE_DISPLAY_MODE) + +static gboolean +cc_display_mode_dbus_equal (const CcDisplayModeDBus *m1, + const CcDisplayModeDBus *m2) +{ + if (!m1 && !m2) + return TRUE; + else if (!m1 || !m2) + return FALSE; + + return m1->width == m2->width && + m1->height == m2->height && + m1->refresh_rate == m2->refresh_rate && + (m1->flags & MODE_INTERLACED) == (m2->flags & MODE_INTERLACED); +} + +static gboolean +cc_display_mode_dbus_is_clone_mode (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + return !self->id; +} + +static void +cc_display_mode_dbus_get_resolution (CcDisplayMode *pself, + int *w, int *h) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + if (w) + *w = self->width; + if (h) + *h = self->height; +} + +static GArray * cc_display_mode_dbus_get_supported_scales (CcDisplayMode *pself); + +static double +cc_display_mode_dbus_get_preferred_scale (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + return self->preferred_scale; +} + +static gboolean +cc_display_mode_dbus_is_supported_scale (CcDisplayMode *pself, + double scale) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + guint i; + for (i = 0; i < self->supported_scales->len; i++) + { + double v = g_array_index (self->supported_scales, double, i); + + if (G_APPROX_VALUE (v, scale, DBL_EPSILON)) + return TRUE; + } + return FALSE; +} + + +static gboolean +cc_display_mode_dbus_is_interlaced (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + return !!(self->flags & MODE_INTERLACED); +} + +static gboolean +cc_display_mode_dbus_is_preferred (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + return !!(self->flags & MODE_PREFERRED); +} + +static int +cc_display_mode_dbus_get_freq (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + return self->refresh_rate; +} + +static double +cc_display_mode_dbus_get_freq_f (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + return self->refresh_rate; +} + +static void +cc_display_mode_dbus_init (CcDisplayModeDBus *self) +{ + self->supported_scales = g_array_new (FALSE, FALSE, sizeof (double)); +} + +static void +cc_display_mode_dbus_finalize (GObject *object) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (object); + + g_free (self->id); + g_array_free (self->supported_scales, TRUE); + + G_OBJECT_CLASS (cc_display_mode_dbus_parent_class)->finalize (object); +} + +static void +cc_display_mode_dbus_class_init (CcDisplayModeDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcDisplayModeClass *parent_class = CC_DISPLAY_MODE_CLASS (klass); + + gobject_class->finalize = cc_display_mode_dbus_finalize; + + parent_class->is_clone_mode = cc_display_mode_dbus_is_clone_mode; + parent_class->get_resolution = cc_display_mode_dbus_get_resolution; + parent_class->get_supported_scales = cc_display_mode_dbus_get_supported_scales; + parent_class->get_preferred_scale = cc_display_mode_dbus_get_preferred_scale; + parent_class->is_interlaced = cc_display_mode_dbus_is_interlaced; + parent_class->is_preferred = cc_display_mode_dbus_is_preferred; + parent_class->get_freq = cc_display_mode_dbus_get_freq; + parent_class->get_freq_f = cc_display_mode_dbus_get_freq_f; +} + +static CcDisplayModeDBus * +cc_display_mode_dbus_new_virtual (int width, + int height, + double preferred_scale, + GArray *supported_scales) +{ + g_autoptr(GVariant) properties_variant = NULL; + CcDisplayModeDBus *self; + + self = g_object_new (CC_TYPE_DISPLAY_MODE_DBUS, NULL); + + self->width = width; + self->height = height; + self->preferred_scale = preferred_scale; + self->supported_scales = g_array_ref (supported_scales); + + return self; +} + +static CcDisplayModeDBus * +cc_display_mode_dbus_new (CcDisplayMonitorDBus *monitor, + GVariant *variant) +{ + double d; + g_autoptr(GVariantIter) scales_iter = NULL; + g_autoptr(GVariant) properties_variant = NULL; + gboolean is_current; + gboolean is_preferred; + gboolean is_interlaced; + CcDisplayModeDBus *self = g_object_new (CC_TYPE_DISPLAY_MODE_DBUS, NULL); + + self->monitor = monitor; + + g_variant_get (variant, "(" MODE_BASE_FORMAT "@a{sv})", + &self->id, + &self->width, + &self->height, + &self->refresh_rate, + &self->preferred_scale, + &scales_iter, + &properties_variant); + + while (g_variant_iter_next (scales_iter, "d", &d)) + g_array_append_val (self->supported_scales, d); + + if (!g_variant_lookup (properties_variant, "is-current", "b", &is_current)) + is_current = FALSE; + if (!g_variant_lookup (properties_variant, "is-preferred", "b", &is_preferred)) + is_preferred = FALSE; + if (!g_variant_lookup (properties_variant, "is-interlaced", "b", &is_interlaced)) + is_interlaced = FALSE; + + if (is_current) + self->flags |= MODE_CURRENT; + if (is_preferred) + self->flags |= MODE_PREFERRED; + if (is_interlaced) + self->flags |= MODE_INTERLACED; + + return self; +} + + +#define CC_TYPE_DISPLAY_LOGICAL_MONITOR (cc_display_logical_monitor_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayLogicalMonitor, cc_display_logical_monitor, + CC, DISPLAY_LOGICAL_MONITOR, GObject) + +struct _CcDisplayLogicalMonitor +{ + GObject parent_instance; + + int x; + int y; + double scale; + CcDisplayRotation rotation; + gboolean primary; + + GHashTable *monitors; +}; + +G_DEFINE_TYPE (CcDisplayLogicalMonitor, + cc_display_logical_monitor, + G_TYPE_OBJECT) + +static gboolean +cc_display_logical_monitor_equal (const CcDisplayLogicalMonitor *m1, + const CcDisplayLogicalMonitor *m2) +{ + if (!m1 && !m2) + return TRUE; + else if (!m1 || !m2) + return FALSE; + + return m1->x == m2->x && + m1->y == m2->y && + G_APPROX_VALUE (m1->scale, m2->scale, DBL_EPSILON) && + m1->rotation == m2->rotation && + m1->primary == m2->primary; +} + +static void +cc_display_logical_monitor_init (CcDisplayLogicalMonitor *self) +{ + self->scale = 1.0; + self->monitors = g_hash_table_new (NULL, NULL); +} + +static void +cc_display_logical_monitor_finalize (GObject *object) +{ + CcDisplayLogicalMonitor *self = CC_DISPLAY_LOGICAL_MONITOR (object); + + g_warn_if_fail (g_hash_table_size (self->monitors) == 0); + g_clear_pointer (&self->monitors, g_hash_table_destroy); + + G_OBJECT_CLASS (cc_display_logical_monitor_parent_class)->finalize (object); +} + +static void +cc_display_logical_monitor_class_init (CcDisplayLogicalMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = cc_display_logical_monitor_finalize; +} + + +typedef enum _CcDisplayMonitorUnderscanning +{ + UNDERSCANNING_UNSUPPORTED = 0, + UNDERSCANNING_DISABLED, + UNDERSCANNING_ENABLED +} CcDisplayMonitorUnderscanning; + +struct _CcDisplayMonitorDBus +{ + CcDisplayMonitor parent_instance; + CcDisplayConfigDBus *config; + + gchar *connector_name; + gchar *vendor_name; + gchar *product_name; + gchar *product_serial; + gchar *display_name; + + int width_mm; + int height_mm; + gboolean builtin; + CcDisplayMonitorUnderscanning underscanning; + CcDisplayMonitorPrivacy privacy_screen; + int max_width; + int max_height; + + GList *modes; + CcDisplayMode *current_mode; + CcDisplayMode *preferred_mode; + + CcDisplayLogicalMonitor *logical_monitor; +}; + +G_DEFINE_TYPE (CcDisplayMonitorDBus, + cc_display_monitor_dbus, + CC_TYPE_DISPLAY_MONITOR) + +static void +register_logical_monitor (CcDisplayConfigDBus *self, + CcDisplayLogicalMonitor *logical_monitor); +static void +cc_display_config_dbus_set_primary (CcDisplayConfigDBus *self, + CcDisplayMonitorDBus *new_primary); +static void +cc_display_config_dbus_unset_primary (CcDisplayConfigDBus *self, + CcDisplayMonitorDBus *old_primary); +static void +cc_display_config_dbus_ensure_non_offset_coords (CcDisplayConfigDBus *self); +static void +cc_display_config_dbus_append_right (CcDisplayConfigDBus *self, + CcDisplayLogicalMonitor *monitor); +static void +cc_display_config_dbus_make_linear (CcDisplayConfigDBus *self); + + +static const char * +cc_display_monitor_dbus_get_display_name (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->display_name) + return self->display_name; + + return self->connector_name; +} + +static const char * +cc_display_monitor_dbus_get_connector_name (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->connector_name; +} + +static gboolean +cc_display_monitor_dbus_is_builtin (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->builtin; +} + +static gboolean +cc_display_monitor_dbus_is_primary (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->logical_monitor) + return self->logical_monitor->primary; + + return FALSE; +} + +static void +cc_display_monitor_dbus_set_primary (CcDisplayMonitor *pself, + gboolean primary) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (primary) + cc_display_config_dbus_set_primary (self->config, self); + else + cc_display_config_dbus_unset_primary (self->config, self); +} + +static gboolean +cc_display_monitor_dbus_is_active (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->logical_monitor != NULL; +} + +static void +cc_display_monitor_dbus_set_logical_monitor (CcDisplayMonitorDBus *self, + CcDisplayLogicalMonitor *logical_monitor) +{ + gboolean was_primary = FALSE; + + if (self->logical_monitor) + { + was_primary = self->logical_monitor->primary; + if (was_primary) + cc_display_config_dbus_unset_primary (self->config, self); + g_hash_table_remove (self->logical_monitor->monitors, self); + g_object_unref (self->logical_monitor); + } + + self->logical_monitor = logical_monitor; + + if (self->logical_monitor) + { + g_hash_table_add (self->logical_monitor->monitors, self); + g_object_ref (self->logical_monitor); + /* unset primary with NULL will select this monitor if it is the only one.*/ + if (was_primary) + cc_display_config_dbus_set_primary (self->config, self); + else + cc_display_config_dbus_unset_primary (self->config, NULL); + } +} + +static void +cc_display_monitor_dbus_set_active (CcDisplayMonitor *pself, + gboolean active) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (!self->current_mode && active) + { + if (self->preferred_mode) + self->current_mode = self->preferred_mode; + else if (self->modes) + self->current_mode = (CcDisplayMode *) self->modes->data; + else + g_warning ("Couldn't find a mode to activate monitor at %s", self->connector_name); + } + + if (!self->logical_monitor && active) + { + CcDisplayLogicalMonitor *logical_monitor; + logical_monitor = g_object_new (CC_TYPE_DISPLAY_LOGICAL_MONITOR, NULL); + cc_display_monitor_dbus_set_logical_monitor (self, logical_monitor); + cc_display_config_dbus_append_right (self->config, logical_monitor); + register_logical_monitor (self->config, logical_monitor); + } + else if (self->logical_monitor && !active) + { + cc_display_monitor_dbus_set_logical_monitor (self, NULL); + } + + g_signal_emit_by_name (self, "active"); +} + +static CcDisplayRotation +cc_display_monitor_dbus_get_rotation (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->logical_monitor) + return self->logical_monitor->rotation; + + return CC_DISPLAY_ROTATION_NONE; +} + +static void +cc_display_monitor_dbus_set_rotation (CcDisplayMonitor *pself, + CcDisplayRotation rotation) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (!self->logical_monitor) + return; + + if (self->logical_monitor->rotation != rotation) + { + self->logical_monitor->rotation = rotation; + + g_signal_emit_by_name (self, "rotation"); + } +} + +static gboolean +cc_display_monitor_dbus_supports_rotation (CcDisplayMonitor *pself, + CcDisplayRotation rotation) +{ + return TRUE; +} + +static void +cc_display_monitor_dbus_get_physical_size (CcDisplayMonitor *pself, + int *w, int *h) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (w) + *w = self->width_mm; + if (h) + *h = self->height_mm; +} + +static void +cc_display_monitor_dbus_get_geometry (CcDisplayMonitor *pself, + int *x, int *y, int *w, int *h) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + CcDisplayMode *mode = NULL; + + if (self->logical_monitor) + { + if (x) + *x = self->logical_monitor->x; + if (y) + *y = self->logical_monitor->y; + } + else + { + if (x) + *x = -1; + if (y) + *y = -1; + } + + if (self->current_mode) + mode = self->current_mode; + else if (self->preferred_mode) + mode = self->preferred_mode; + else if (self->modes) + mode = CC_DISPLAY_MODE (self->modes->data); + + if (mode) + cc_display_mode_get_resolution (mode, w, h); + else + { + g_warning ("Monitor at %s has no modes?", self->connector_name); + if (w) + *w = -1; + if (h) + *h = -1; + } +} + +static CcDisplayMode * +cc_display_monitor_dbus_get_mode (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->current_mode; +} + +static CcDisplayMode * +cc_display_monitor_dbus_get_preferred_mode (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->preferred_mode; +} + +static guint32 +cc_display_monitor_dbus_get_id (CcDisplayMonitor *pself) +{ + return 0; +} + +static GList * +cc_display_monitor_dbus_get_modes (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->modes; +} + +static gboolean +cc_display_monitor_dbus_supports_underscanning (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->underscanning != UNDERSCANNING_UNSUPPORTED; +} + +static gboolean +cc_display_monitor_dbus_get_underscanning (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->underscanning == UNDERSCANNING_ENABLED; +} + +static CcDisplayMonitorPrivacy +cc_display_monitor_dbus_get_privacy (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->privacy_screen; +} + +static void +cc_display_monitor_dbus_set_underscanning (CcDisplayMonitor *pself, + gboolean underscanning) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->underscanning == UNDERSCANNING_UNSUPPORTED) + return; + + if (underscanning) + self->underscanning = UNDERSCANNING_ENABLED; + else + self->underscanning = UNDERSCANNING_DISABLED; +} + +static CcDisplayMode * +cc_display_monitor_dbus_get_closest_mode (CcDisplayMonitorDBus *self, + CcDisplayModeDBus *mode) +{ + CcDisplayModeDBus *best = NULL; + GList *l; + + for (l = self->modes; l != NULL; l = l->next) + { + CcDisplayModeDBus *similar = l->data; + + if (similar->width != mode->width || + similar->height != mode->height) + continue; + + if (similar->refresh_rate == mode->refresh_rate && + (similar->flags & MODE_INTERLACED) == (mode->flags & MODE_INTERLACED)) + { + best = similar; + break; + } + + /* There might be a better heuristic. */ + if (!best || best->refresh_rate < similar->refresh_rate) + { + best = similar; + continue; + } + } + + return CC_DISPLAY_MODE (best); +} + +static void +cc_display_monitor_dbus_set_mode (CcDisplayMonitor *pself, + CcDisplayMode *new_mode) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + CcDisplayMode *mode; + + g_return_if_fail (new_mode != NULL); + + mode = cc_display_monitor_dbus_get_closest_mode (self, CC_DISPLAY_MODE_DBUS (new_mode)); + + self->current_mode = mode; + + if (!cc_display_mode_dbus_is_supported_scale (mode, cc_display_monitor_get_scale (pself))) + cc_display_monitor_set_scale (pself, cc_display_mode_get_preferred_scale (mode)); + + g_signal_emit_by_name (self, "mode"); +} + +static void +cc_display_monitor_dbus_set_compatible_clone_mode (CcDisplayMonitor *pself, + CcDisplayMode *clone_mode) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + GList *l; + CcDisplayMode *best_mode = NULL; + int clone_width, clone_height; + + g_return_if_fail (cc_display_mode_is_clone_mode (clone_mode)); + + cc_display_mode_get_resolution (clone_mode, &clone_width, &clone_height); + + for (l = self->modes; l; l = l->next) + { + CcDisplayMode *mode = l->data; + int width, height; + + cc_display_mode_get_resolution (mode, &width, &height); + if (width != clone_width || height != clone_height) + continue; + + if (!best_mode) + { + best_mode = mode; + continue; + } + + if (cc_display_mode_get_freq_f (mode) > + cc_display_mode_get_freq_f (best_mode)) + best_mode = mode; + } + + g_return_if_fail (best_mode); + + cc_display_monitor_set_mode (CC_DISPLAY_MONITOR (self), best_mode); +} + +static void +cc_display_monitor_dbus_set_position (CcDisplayMonitor *pself, + int x, int y) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->logical_monitor) + { + gboolean notify = FALSE; + + if (self->logical_monitor->x != x || self->logical_monitor->y != y) + notify = TRUE; + + self->logical_monitor->x = x; + self->logical_monitor->y = y; + + if (notify) + g_signal_emit_by_name (self, "position-changed"); + } + +} + +static double +cc_display_monitor_dbus_get_scale (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->logical_monitor) + return self->logical_monitor->scale; + + return 1.0; +} + +static void +cc_display_monitor_dbus_set_scale (CcDisplayMonitor *pself, + double scale) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (!self->current_mode) + return; + + if (!cc_display_mode_dbus_is_supported_scale (self->current_mode, scale)) + return; + + if (!self->logical_monitor) + return; + + if (!G_APPROX_VALUE (self->logical_monitor->scale, scale, DBL_EPSILON)) + { + self->logical_monitor->scale = scale; + + g_signal_emit_by_name (self, "scale"); + } +} + +static void +cc_display_monitor_dbus_init (CcDisplayMonitorDBus *self) +{ + self->underscanning = UNDERSCANNING_UNSUPPORTED; + self->max_width = G_MAXINT; + self->max_height = G_MAXINT; +} + +static void +cc_display_monitor_dbus_finalize (GObject *object) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (object); + + g_free (self->connector_name); + g_free (self->vendor_name); + g_free (self->product_name); + g_free (self->product_serial); + g_free (self->display_name); + + g_list_free_full (self->modes, g_object_unref); + + if (self->logical_monitor) + { + g_hash_table_remove (self->logical_monitor->monitors, self); + g_object_unref (self->logical_monitor); + } + + G_OBJECT_CLASS (cc_display_monitor_dbus_parent_class)->finalize (object); +} + +static void +cc_display_monitor_dbus_class_init (CcDisplayMonitorDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcDisplayMonitorClass *parent_class = CC_DISPLAY_MONITOR_CLASS (klass); + + gobject_class->finalize = cc_display_monitor_dbus_finalize; + + parent_class->get_display_name = cc_display_monitor_dbus_get_display_name; + parent_class->get_connector_name = cc_display_monitor_dbus_get_connector_name; + parent_class->is_builtin = cc_display_monitor_dbus_is_builtin; + parent_class->is_primary = cc_display_monitor_dbus_is_primary; + parent_class->set_primary = cc_display_monitor_dbus_set_primary; + parent_class->is_active = cc_display_monitor_dbus_is_active; + parent_class->set_active = cc_display_monitor_dbus_set_active; + parent_class->get_rotation = cc_display_monitor_dbus_get_rotation; + parent_class->set_rotation = cc_display_monitor_dbus_set_rotation; + parent_class->supports_rotation = cc_display_monitor_dbus_supports_rotation; + parent_class->get_physical_size = cc_display_monitor_dbus_get_physical_size; + parent_class->get_geometry = cc_display_monitor_dbus_get_geometry; + parent_class->get_mode = cc_display_monitor_dbus_get_mode; + parent_class->get_preferred_mode = cc_display_monitor_dbus_get_preferred_mode; + parent_class->get_id = cc_display_monitor_dbus_get_id; + parent_class->get_modes = cc_display_monitor_dbus_get_modes; + parent_class->supports_underscanning = cc_display_monitor_dbus_supports_underscanning; + parent_class->get_underscanning = cc_display_monitor_dbus_get_underscanning; + parent_class->set_underscanning = cc_display_monitor_dbus_set_underscanning; + parent_class->get_privacy = cc_display_monitor_dbus_get_privacy; + parent_class->set_mode = cc_display_monitor_dbus_set_mode; + parent_class->set_compatible_clone_mode = cc_display_monitor_dbus_set_compatible_clone_mode; + parent_class->set_position = cc_display_monitor_dbus_set_position; + parent_class->get_scale = cc_display_monitor_dbus_get_scale; + parent_class->set_scale = cc_display_monitor_dbus_set_scale; +} + +static void +construct_modes (CcDisplayMonitorDBus *self, + GVariantIter *modes) +{ + CcDisplayModeDBus *mode; + + while (TRUE) + { + g_autoptr(GVariant) variant = NULL; + + if (!g_variant_iter_next (modes, "@"MODE_FORMAT, &variant)) + break; + + mode = cc_display_mode_dbus_new (self, variant); + self->modes = g_list_prepend (self->modes, mode); + + if (mode->flags & MODE_PREFERRED) + self->preferred_mode = CC_DISPLAY_MODE (mode); + if (mode->flags & MODE_CURRENT) + self->current_mode = CC_DISPLAY_MODE (mode); + } + + self->modes = g_list_reverse (self->modes); +} + +static CcDisplayMonitorDBus * +cc_display_monitor_dbus_new (GVariant *variant, + CcDisplayConfigDBus *config) +{ + CcDisplayMonitorDBus *self = g_object_new (CC_TYPE_DISPLAY_MONITOR_DBUS, NULL); + gchar *s1, *s2, *s3, *s4; + g_autoptr(GVariantIter) modes = NULL; + g_autoptr(GVariantIter) props = NULL; + + self->config = config; + + g_variant_get (variant, MONITOR_FORMAT, + &s1, &s2, &s3, &s4, &modes, &props); + self->connector_name = s1; + self->vendor_name = s2; + self->product_name = s3; + self->product_serial = s4; + + construct_modes (self, modes); + + while (TRUE) + { + const char *s; + g_autoptr(GVariant) v = NULL; + + if (!g_variant_iter_next (props, "{&sv}", &s, &v)) + break; + + if (g_str_equal (s, "width-mm")) + { + g_variant_get (v, "i", &self->width_mm); + } + else if (g_str_equal (s, "height-mm")) + { + g_variant_get (v, "i", &self->height_mm); + } + else if (g_str_equal (s, "is-underscanning")) + { + gboolean underscanning = FALSE; + g_variant_get (v, "b", &underscanning); + if (underscanning) + self->underscanning = UNDERSCANNING_ENABLED; + else + self->underscanning = UNDERSCANNING_DISABLED; + } + else if (g_str_equal (s, "max-screen-size")) + { + g_variant_get (v, "ii", &self->max_width, &self->max_height); + } + else if (g_str_equal (s, "is-builtin")) + { + g_variant_get (v, "b", &self->builtin); + } + else if (g_str_equal (s, "display-name")) + { + g_variant_get (v, "s", &self->display_name); + } + else if (g_str_equal (s, "privacy-screen-state")) + { + gboolean enabled; + gboolean locked; + g_variant_get (v, "(bb)", &enabled, &locked); + + if (enabled) + self->privacy_screen = CC_DISPLAY_MONITOR_PRIVACY_ENABLED; + else + self->privacy_screen = CC_DISPLAY_MONITOR_PRIVACY_DISABLED; + + if (locked) + self->privacy_screen |= CC_DISPLAY_MONITOR_PRIVACY_LOCKED; + } + } + + return self; +} + + +typedef enum _CcDisplayLayoutMode +{ + CC_DISPLAY_LAYOUT_MODE_LOGICAL = 1, + CC_DISPLAY_LAYOUT_MODE_PHYSICAL = 2 +} CcDisplayLayoutMode; + +typedef enum _CcDisplayConfigMethod +{ + CC_DISPLAY_CONFIG_METHOD_VERIFY = 0, + CC_DISPLAY_CONFIG_METHOD_TEMPORARY = 1, + CC_DISPLAY_CONFIG_METHOD_PERSISTENT = 2 +} CcDisplayConfigMethod; + +struct _CcDisplayConfigDBus +{ + CcDisplayConfig parent_instance; + + GVariant *state; + GDBusConnection *connection; + GDBusProxy *proxy; + + int min_width; + int min_height; + + guint panel_orientation_managed; + + guint32 serial; + gboolean supports_mirroring; + gboolean supports_changing_layout_mode; + gboolean global_scale_required; + CcDisplayLayoutMode layout_mode; + + GList *monitors; + CcDisplayMonitorDBus *primary; + + GHashTable *logical_monitors; +}; + +G_DEFINE_TYPE (CcDisplayConfigDBus, + cc_display_config_dbus, + CC_TYPE_DISPLAY_CONFIG) + +enum +{ + PROP_0, + PROP_STATE, + PROP_CONNECTION, +}; + +static GList * +cc_display_config_dbus_get_monitors (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return self->monitors; +} + +static GVariant * +build_monitors_variant (GHashTable *monitors) +{ + GVariantBuilder builder; + GHashTableIter iter; + CcDisplayMonitorDBus *monitor; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_hash_table_iter_init (&iter, monitors); + + while (g_hash_table_iter_next (&iter, (void **) &monitor, NULL)) + { + GVariantBuilder props_builder; + CcDisplayModeDBus *mode_dbus; + + if (!monitor->current_mode) + continue; + + g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&props_builder, "{sv}", + "underscanning", + g_variant_new_boolean (monitor->underscanning == UNDERSCANNING_ENABLED)); + + mode_dbus = CC_DISPLAY_MODE_DBUS (monitor->current_mode); + g_variant_builder_add (&builder, "(ss@*)", + monitor->connector_name, + mode_dbus->id, + g_variant_builder_end (&props_builder)); + } + + return g_variant_builder_end (&builder); +} + +static GVariant * +build_logical_monitors_parameter (CcDisplayConfigDBus *self) +{ + GVariantBuilder builder; + GHashTableIter iter; + CcDisplayLogicalMonitor *logical_monitor; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(iiduba(ssa{sv}))")); + g_hash_table_iter_init (&iter, self->logical_monitors); + + while (g_hash_table_iter_next (&iter, (void **) &logical_monitor, NULL)) + g_variant_builder_add (&builder, "(iidub@*)", + logical_monitor->x, + logical_monitor->y, + logical_monitor->scale, + logical_monitor->rotation, + logical_monitor->primary, + build_monitors_variant (logical_monitor->monitors)); + + return g_variant_builder_end (&builder); +} + +static GVariant * +build_apply_parameters (CcDisplayConfigDBus *self, + CcDisplayConfigMethod method) +{ + GVariantBuilder props_builder; + g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}")); + + if (self->supports_changing_layout_mode) + g_variant_builder_add (&props_builder, "{sv}", + "layout-mode", g_variant_new_uint32 (self->layout_mode)); + + return g_variant_new ("(uu@*@*)", + self->serial, + method, + build_logical_monitors_parameter (self), + g_variant_builder_end (&props_builder)); +} + +static gboolean +config_apply (CcDisplayConfigDBus *self, + CcDisplayConfigMethod method, + GError **error) +{ + g_autoptr(GVariant) retval = NULL; + + cc_display_config_dbus_ensure_non_offset_coords (self); + + retval = g_dbus_proxy_call_sync (self->proxy, + "ApplyMonitorsConfig", + build_apply_parameters (self, method), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + error); + return retval != NULL; +} + +static gboolean +cc_display_config_dbus_is_applicable (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + g_autoptr(GError) error = NULL; + + if (!config_apply (self, CC_DISPLAY_CONFIG_METHOD_VERIFY, &error)) + { + g_warning ("Config not applicable: %s", error->message); + return FALSE; + } + else + { + return TRUE; + } +} + +static CcDisplayMonitorDBus * +monitor_from_spec (CcDisplayConfigDBus *self, + const gchar *connector, + const gchar *vendor, + const gchar *product, + const gchar *serial) +{ + GList *l; + for (l = self->monitors; l != NULL; l = l->next) + { + CcDisplayMonitorDBus *m = l->data; + if (g_str_equal (m->connector_name, connector) && + g_str_equal (m->vendor_name, vendor) && + g_str_equal (m->product_name, product) && + g_str_equal (m->product_serial, serial)) + return m; + } + return NULL; +} + +static gboolean +cc_display_config_dbus_equal (CcDisplayConfig *pself, + CcDisplayConfig *pother) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + CcDisplayConfigDBus *other = CC_DISPLAY_CONFIG_DBUS (pother); + GList *l; + + g_return_val_if_fail (pself, FALSE); + g_return_val_if_fail (pother, FALSE); + + cc_display_config_dbus_ensure_non_offset_coords (self); + cc_display_config_dbus_ensure_non_offset_coords (other); + + for (l = self->monitors; l != NULL; l = l->next) + { + CcDisplayMonitorDBus *m1 = l->data; + CcDisplayMonitorDBus *m2 = monitor_from_spec (other, + m1->connector_name, + m1->vendor_name, + m1->product_name, + m1->product_serial); + if (!m2) + return FALSE; + + if (m1->underscanning != m2->underscanning) + return FALSE; + + if (!cc_display_logical_monitor_equal (m1->logical_monitor, m2->logical_monitor)) + return FALSE; + + /* Modes should not be compared if both monitors have no logical monitor. */ + if (m1->logical_monitor == NULL && m2->logical_monitor == NULL) + continue; + + if (!cc_display_mode_dbus_equal (CC_DISPLAY_MODE_DBUS (m1->current_mode), + CC_DISPLAY_MODE_DBUS (m2->current_mode))) + return FALSE; + } + + return TRUE; +} + +static void +cc_display_config_dbus_set_primary (CcDisplayConfigDBus *self, + CcDisplayMonitorDBus *new_primary) +{ + if (self->primary == new_primary) + return; + + if (!new_primary->logical_monitor) + return; + + if (self->primary && self->primary->logical_monitor) + { + self->primary->logical_monitor->primary = FALSE; + g_signal_emit_by_name (self->primary, "primary"); + } + + self->primary = new_primary; + self->primary->logical_monitor->primary = TRUE; + + g_signal_emit_by_name (self->primary, "primary"); + g_signal_emit_by_name (self, "primary"); +} + +static void +cc_display_config_dbus_unset_primary (CcDisplayConfigDBus *self, + CcDisplayMonitorDBus *old_primary) +{ + GList *l; + + if (self->primary != old_primary) + return; + + for (l = self->monitors; l != NULL; l = l->next) + { + CcDisplayMonitorDBus *monitor = l->data; + if (monitor->logical_monitor && + monitor != old_primary) + { + cc_display_config_dbus_set_primary (self, monitor); + break; + } + } + + if (self->primary == old_primary) + self->primary = NULL; +} + +static gboolean +cc_display_config_dbus_is_cloning (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + guint n_active_monitors = 0; + GList *l; + + for (l = self->monitors; l != NULL; l = l->next) + if (cc_display_monitor_is_active (CC_DISPLAY_MONITOR (l->data))) + n_active_monitors += 1; + + return n_active_monitors > 1 && g_hash_table_size (self->logical_monitors) == 1; +} + +static void +cc_display_config_dbus_set_cloning (CcDisplayConfig *pself, + gboolean clone) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + gboolean is_cloning = cc_display_config_is_cloning (pself); + CcDisplayLogicalMonitor *logical_monitor; + GList *l; + + if (clone && !is_cloning) + { + logical_monitor = g_object_new (CC_TYPE_DISPLAY_LOGICAL_MONITOR, NULL); + for (l = self->monitors; l != NULL; l = l->next) + cc_display_monitor_dbus_set_logical_monitor (CC_DISPLAY_MONITOR_DBUS (l->data), + logical_monitor); + register_logical_monitor (self, logical_monitor); + } + else if (!clone && is_cloning) + { + for (l = self->monitors; l != NULL; l = l->next) + { + logical_monitor = g_object_new (CC_TYPE_DISPLAY_LOGICAL_MONITOR, NULL); + cc_display_monitor_dbus_set_logical_monitor (CC_DISPLAY_MONITOR_DBUS (l->data), + logical_monitor); + register_logical_monitor (self, logical_monitor); + } + cc_display_config_dbus_make_linear (self); + } +} + +static gboolean +mode_supports_scale (CcDisplayMode *mode, + double scale) +{ + g_autoptr(GArray) scales = NULL; + int i; + + scales = cc_display_mode_get_supported_scales (mode); + for (i = 0; i < scales->len; i++) + { + if (G_APPROX_VALUE (scale, g_array_index (scales, double, i), + DBL_EPSILON)) + return TRUE; + } + + return FALSE; +} + +static void +remove_unsupported_scales (CcDisplayMode *mode, + GArray *supported_scales) +{ + g_autoptr(GArray) mode_scales = NULL; + int i; + + mode_scales = cc_display_mode_get_supported_scales (mode); + i = 0; + while (i < supported_scales->len) + { + double scale; + + if (i == supported_scales->len) + break; + + scale = g_array_index (supported_scales, double, i); + + if (mode_supports_scale (mode, scale)) + { + i++; + continue; + } + + g_array_remove_range (supported_scales, i, 1); + } +} + +static gboolean +monitor_has_compatible_clone_mode (CcDisplayMonitorDBus *monitor, + CcDisplayModeDBus *mode, + GArray *supported_scales) +{ + GList *l; + + for (l = monitor->modes; l; l = l->next) + { + CcDisplayModeDBus *other_mode = l->data; + + if (other_mode->width != mode->width || + other_mode->height != mode->height) + continue; + + if ((other_mode->flags & MODE_INTERLACED) != + (mode->flags & MODE_INTERLACED)) + continue; + + remove_unsupported_scales (CC_DISPLAY_MODE (other_mode), supported_scales); + + return TRUE; + } + + return FALSE; +} + +static gboolean +monitors_has_compatible_clone_mode (CcDisplayConfigDBus *self, + CcDisplayModeDBus *mode, + GArray *supported_scales) +{ + GList *l; + + for (l = self->monitors; l; l = l->next) + { + CcDisplayMonitorDBus *monitor = l->data; + + if (!monitor_has_compatible_clone_mode (monitor, mode, supported_scales)) + return FALSE; + } + + return TRUE; +} + +static gboolean +is_mode_better (CcDisplayModeDBus *mode, + CcDisplayModeDBus *other_mode) +{ + if (mode->width * mode->height > other_mode->width * other_mode->height) + return TRUE; + else if (mode->width * mode->height < other_mode->width * other_mode->height) + return FALSE; + + if (!(mode->flags & MODE_INTERLACED) && + (other_mode->flags & MODE_INTERLACED)) + return TRUE; + + return FALSE; +} + +static GList * +cc_display_config_dbus_generate_cloning_modes (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + CcDisplayMonitorDBus *base_monitor = NULL; + GList *l; + GList *clone_modes = NULL; + CcDisplayModeDBus *best_mode = NULL; + + for (l = self->monitors; l; l = l->next) + { + CcDisplayMonitor *monitor = l->data; + + if (cc_display_monitor_is_active (monitor)) + { + base_monitor = CC_DISPLAY_MONITOR_DBUS (monitor); + break; + } + } + + if (!base_monitor) + return NULL; + + for (l = base_monitor->modes; l; l = l->next) + { + CcDisplayModeDBus *mode = l->data; + CcDisplayModeDBus *virtual_mode; + g_autoptr (GArray) supported_scales = NULL; + + supported_scales = + cc_display_mode_get_supported_scales (CC_DISPLAY_MODE (mode)); + + if (!monitors_has_compatible_clone_mode (self, mode, supported_scales)) + continue; + + virtual_mode = cc_display_mode_dbus_new_virtual (mode->width, + mode->height, + mode->preferred_scale, + supported_scales); + clone_modes = g_list_append (clone_modes, virtual_mode); + + if (!best_mode || is_mode_better (virtual_mode, best_mode)) + best_mode = virtual_mode; + } + + best_mode->flags |= MODE_PREFERRED; + + return clone_modes; +} + +static gboolean +cc_display_config_dbus_apply (CcDisplayConfig *pself, + GError **error) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return config_apply (self, CC_DISPLAY_CONFIG_METHOD_PERSISTENT, error); +} + +static gboolean +cc_display_config_dbus_is_layout_logical (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return self->layout_mode == CC_DISPLAY_LAYOUT_MODE_LOGICAL; +} + +static gboolean +is_scale_allowed_by_active_monitors (CcDisplayConfigDBus *self, + CcDisplayMode *mode, + double scale); + +static gboolean +is_scaled_mode_allowed (CcDisplayConfigDBus *self, + CcDisplayModeDBus *mode, + double scale) +{ + gint width, height; + + /* Do the math as if the monitor is always in landscape mode. */ + width = round (mode->width / scale); + height = round (mode->height / scale); + + if (MAX (width, height) < self->min_width || + MIN (width, height) < self->min_height) + return FALSE; + + if (!self->global_scale_required) + return TRUE; + + return is_scale_allowed_by_active_monitors (self, CC_DISPLAY_MODE (mode), scale); +} + +static gboolean +is_scale_allowed_by_active_monitors (CcDisplayConfigDBus *self, + CcDisplayMode *mode, + double scale) +{ + GList *l; + + for (l = self->monitors; l != NULL; l = l->next) + { + CcDisplayMonitorDBus *m = CC_DISPLAY_MONITOR_DBUS (l->data); + + if (!cc_display_monitor_is_active (CC_DISPLAY_MONITOR (m))) + continue; + + if (!cc_display_mode_dbus_is_supported_scale (mode, scale)) + return FALSE; + } + + return TRUE; +} + +static GArray * +cc_display_mode_dbus_get_supported_scales (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + CcDisplayConfig *config = CC_DISPLAY_CONFIG (self->monitor->config); + + if (cc_display_config_is_cloning (config)) + { + GArray *scales = g_array_copy (self->supported_scales); + int i; + + for (i = scales->len - 1; i >= 0; i--) + { + double scale = g_array_index (scales, double, i); + + if (!is_scale_allowed_by_active_monitors (self->monitor->config, + pself, scale)) + g_array_remove_index (scales, i); + } + + return g_steal_pointer (&scales); + } + + return g_array_ref (self->supported_scales); +} + +static void +filter_out_invalid_scaled_modes (CcDisplayConfigDBus *self) +{ + GList *l; + + for (l = self->monitors; l; l = l->next) + { + CcDisplayMonitorDBus *monitor = l->data; + GList *ll = monitor->modes; + + while (ll != NULL) + { + CcDisplayModeDBus *mode = ll->data; + GList *current = ll; + double current_scale = -1; + int i; + + ll = ll->next; + + if (monitor->current_mode != CC_DISPLAY_MODE (mode) && + monitor->preferred_mode != CC_DISPLAY_MODE (mode) && + !is_scaled_mode_allowed (self, mode, 1.0)) + { + g_clear_object (&mode); + monitor->modes = g_list_delete_link (monitor->modes, current); + continue; + } + + if (monitor->current_mode == CC_DISPLAY_MODE (mode)) + current_scale = cc_display_monitor_dbus_get_scale (CC_DISPLAY_MONITOR (monitor)); + + for (i = mode->supported_scales->len - 1; i >= 0; i--) + { + float scale = g_array_index (mode->supported_scales, double, i); + + if (!G_APPROX_VALUE (scale, current_scale, DBL_EPSILON) && + !G_APPROX_VALUE (scale, mode->preferred_scale, DBL_EPSILON) && + !is_scaled_mode_allowed (self, mode, scale)) + { + g_array_remove_index (mode->supported_scales, i); + } + } + } + } +} + +static void +cc_display_config_dbus_set_minimum_size (CcDisplayConfig *pself, + int width, + int height) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + g_assert (width >= 0 && height >= 0); + g_assert (((self->min_width == 0 && self->min_height == 0) || + (self->min_width >= width && self->min_height >= height)) && + "Minimum size can't be set again to higher values"); + + self->min_width = width; + self->min_height = height; + + filter_out_invalid_scaled_modes (self); +} + +static gboolean +cc_display_config_dbus_is_scaled_mode_valid (CcDisplayConfig *pself, + CcDisplayMode *mode, + double scale) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + if (cc_display_config_is_cloning (pself)) + return is_scale_allowed_by_active_monitors (self, mode, scale); + + return cc_display_mode_dbus_is_supported_scale (mode, scale); +} + +static gboolean +cc_display_config_dbus_get_panel_orientation_managed (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return self->panel_orientation_managed; +} + +static void +cc_display_config_dbus_init (CcDisplayConfigDBus *self) +{ + self->serial = 0; + self->supports_mirroring = TRUE; + self->supports_changing_layout_mode = FALSE; + self->global_scale_required = FALSE; + self->layout_mode = CC_DISPLAY_LAYOUT_MODE_LOGICAL; + self->logical_monitors = g_hash_table_new (NULL, NULL); +} + +static void +remove_logical_monitor (gpointer data, + GObject *object) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (data); + + g_hash_table_remove (self->logical_monitors, object); +} + +static void +register_logical_monitor (CcDisplayConfigDBus *self, + CcDisplayLogicalMonitor *logical_monitor) +{ + g_hash_table_add (self->logical_monitors, logical_monitor); + g_object_weak_ref (G_OBJECT (logical_monitor), remove_logical_monitor, self); + g_object_unref (logical_monitor); +} + +static void +apply_global_scale_requirement (CcDisplayConfigDBus *self, + CcDisplayMonitor *monitor) +{ + GList *l; + double scale = cc_display_monitor_get_scale (monitor); + + for (l = self->monitors; l != NULL; l = l->next) + { + CcDisplayMonitor *m = l->data; + if (m != monitor) + cc_display_monitor_set_scale (m, scale); + } +} + +static void +construct_monitors (CcDisplayConfigDBus *self, + GVariantIter *monitors, + GVariantIter *logical_monitors) +{ + while (TRUE) + { + CcDisplayMonitorDBus *monitor; + g_autoptr(GVariant) variant = NULL; + + if (!g_variant_iter_next (monitors, "@"MONITOR_FORMAT, &variant)) + break; + + monitor = cc_display_monitor_dbus_new (variant, self); + self->monitors = g_list_prepend (self->monitors, monitor); + + if (self->global_scale_required) + g_signal_connect_object (monitor, "scale", + G_CALLBACK (apply_global_scale_requirement), + self, G_CONNECT_SWAPPED); + } + + self->monitors = g_list_reverse (self->monitors); + + while (TRUE) + { + g_autoptr(GVariant) variant = NULL; + CcDisplayLogicalMonitor *logical_monitor; + g_autoptr(GVariantIter) monitor_specs = NULL; + const gchar *s1, *s2, *s3, *s4; + gboolean primary; + + if (!g_variant_iter_next (logical_monitors, "@"LOGICAL_MONITOR_FORMAT, &variant)) + break; + + logical_monitor = g_object_new (CC_TYPE_DISPLAY_LOGICAL_MONITOR, NULL); + g_variant_get (variant, LOGICAL_MONITOR_FORMAT, + &logical_monitor->x, + &logical_monitor->y, + &logical_monitor->scale, + &logical_monitor->rotation, + &primary, + &monitor_specs, + NULL); + + while (g_variant_iter_next (monitor_specs, "(&s&s&s&s)", &s1, &s2, &s3, &s4)) + { + CcDisplayMonitorDBus *m = monitor_from_spec (self, s1, s2, s3, s4); + if (!m) + { + g_warning ("Couldn't find monitor given spec: %s, %s, %s, %s", + s1, s2, s3, s4); + continue; + } + + cc_display_monitor_dbus_set_logical_monitor (m, logical_monitor); + } + + if (g_hash_table_size (logical_monitor->monitors) > 0) + { + if (primary) + { + CcDisplayMonitorDBus *m = NULL; + GHashTableIter iter; + g_hash_table_iter_init (&iter, logical_monitor->monitors); + g_hash_table_iter_next (&iter, (void **) &m, NULL); + + cc_display_config_dbus_set_primary (self, m); + } + } + else + { + g_warning ("Got an empty logical monitor, ignoring"); + } + + register_logical_monitor (self, logical_monitor); + } +} + +static void +update_panel_orientation_managed (CcDisplayConfigDBus *self) +{ + g_autoptr(GVariant) v = NULL; + gboolean panel_orientation_managed = FALSE; + + if (self->proxy != NULL) + { + v = g_dbus_proxy_get_cached_property (self->proxy, "PanelOrientationManaged"); + if (v) + { + panel_orientation_managed = g_variant_get_boolean (v); + } + } + + if (panel_orientation_managed == self->panel_orientation_managed) + return; + + self->panel_orientation_managed = panel_orientation_managed; + g_signal_emit_by_name (self, "panel-orientation-managed", self->panel_orientation_managed); +} + +static void +proxy_properties_changed_cb (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + CcDisplayConfigDBus *self) +{ + GVariantDict dict; + + g_variant_dict_init (&dict, changed_properties); + + if (g_variant_dict_contains (&dict, "PanelOrientationManaged")) + update_panel_orientation_managed (self); +} + +static void +cc_display_config_dbus_constructed (GObject *object) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + g_autoptr(GVariantIter) monitors = NULL; + g_autoptr(GVariantIter) logical_monitors = NULL; + g_autoptr(GVariantIter) props = NULL; + g_autoptr(GError) error = NULL; + + g_variant_get (self->state, + CURRENT_STATE_FORMAT, + &self->serial, + &monitors, + &logical_monitors, + &props); + + while (TRUE) + { + const char *s; + g_autoptr(GVariant) v = NULL; + + if (!g_variant_iter_next (props, "{&sv}", &s, &v)) + break; + + if (g_str_equal (s, "supports-mirroring")) + { + g_variant_get (v, "b", &self->supports_mirroring); + } + else if (g_str_equal (s, "supports-changing-layout-mode")) + { + g_variant_get (v, "b", &self->supports_changing_layout_mode); + } + else if (g_str_equal (s, "global-scale-required")) + { + g_variant_get (v, "b", &self->global_scale_required); + } + else if (g_str_equal (s, "layout-mode")) + { + guint32 u = 0; + g_variant_get (v, "u", &u); + if (u >= CC_DISPLAY_LAYOUT_MODE_LOGICAL && + u <= CC_DISPLAY_LAYOUT_MODE_PHYSICAL) + self->layout_mode = u; + } + } + + construct_monitors (self, monitors, logical_monitors); + filter_out_invalid_scaled_modes (self); + + self->proxy = g_dbus_proxy_new_sync (self->connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.gnome.Mutter.DisplayConfig", + "/org/gnome/Mutter/DisplayConfig", + "org.gnome.Mutter.DisplayConfig", + NULL, + &error); + if (error) + g_warning ("Could not create DisplayConfig proxy: %s", error->message); + + g_signal_connect (self->proxy, "g-properties-changed", + G_CALLBACK (proxy_properties_changed_cb), self); + update_panel_orientation_managed (self); + + G_OBJECT_CLASS (cc_display_config_dbus_parent_class)->constructed (object); +} + +static void +cc_display_config_dbus_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + + switch (prop_id) + { + case PROP_STATE: + self->state = g_value_dup_variant (value); + break; + case PROP_CONNECTION: + self->connection = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_config_dbus_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + + switch (prop_id) + { + case PROP_STATE: + g_value_set_variant (value, self->state); + break; + case PROP_CONNECTION: + g_value_set_object (value, self->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_config_dbus_dispose (GObject *object) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + + if (self->logical_monitors) + { + GHashTableIter iter; + gpointer monitor; + + g_hash_table_iter_init (&iter, self->logical_monitors); + + while (g_hash_table_iter_next (&iter, &monitor, NULL)) + g_object_weak_unref (G_OBJECT (monitor), remove_logical_monitor, self); + } + + G_OBJECT_CLASS (cc_display_config_dbus_parent_class)->dispose (object); +} + +static void +cc_display_config_dbus_finalize (GObject *object) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + + g_clear_pointer (&self->state, g_variant_unref); + g_clear_object (&self->connection); + g_clear_object (&self->proxy); + + g_clear_list (&self->monitors, g_object_unref); + g_clear_pointer (&self->logical_monitors, g_hash_table_destroy); + + G_OBJECT_CLASS (cc_display_config_dbus_parent_class)->finalize (object); +} + +static void +cc_display_config_dbus_class_init (CcDisplayConfigDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcDisplayConfigClass *parent_class = CC_DISPLAY_CONFIG_CLASS (klass); + GParamSpec *pspec; + + gobject_class->constructed = cc_display_config_dbus_constructed; + gobject_class->set_property = cc_display_config_dbus_set_property; + gobject_class->get_property = cc_display_config_dbus_get_property; + gobject_class->dispose = cc_display_config_dbus_dispose; + gobject_class->finalize = cc_display_config_dbus_finalize; + + parent_class->get_monitors = cc_display_config_dbus_get_monitors; + parent_class->is_applicable = cc_display_config_dbus_is_applicable; + parent_class->equal = cc_display_config_dbus_equal; + parent_class->apply = cc_display_config_dbus_apply; + parent_class->is_cloning = cc_display_config_dbus_is_cloning; + parent_class->set_cloning = cc_display_config_dbus_set_cloning; + parent_class->generate_cloning_modes = cc_display_config_dbus_generate_cloning_modes; + parent_class->is_layout_logical = cc_display_config_dbus_is_layout_logical; + parent_class->is_scaled_mode_valid = cc_display_config_dbus_is_scaled_mode_valid; + parent_class->set_minimum_size = cc_display_config_dbus_set_minimum_size; + parent_class->get_panel_orientation_managed = + cc_display_config_dbus_get_panel_orientation_managed; + + pspec = g_param_spec_variant ("state", + "GVariant", + "GVariant", + G_VARIANT_TYPE (CURRENT_STATE_FORMAT), + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (gobject_class, PROP_STATE, pspec); + + pspec = g_param_spec_object ("connection", + "GDBusConnection", + "GDBusConnection", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (gobject_class, PROP_CONNECTION, pspec); +} + +static gint +sort_x_axis (gconstpointer a, gconstpointer b) +{ + const CcDisplayLogicalMonitor *ma = a; + const CcDisplayLogicalMonitor *mb = b; + return ma->x - mb->x; +} + +static gint +sort_y_axis (gconstpointer a, gconstpointer b) +{ + const CcDisplayLogicalMonitor *ma = a; + const CcDisplayLogicalMonitor *mb = b; + return ma->y - mb->y; +} + +static void +add_x_delta (gpointer d1, gpointer d2) +{ + CcDisplayLogicalMonitor *m = d1; + int delta = GPOINTER_TO_INT (d2); + m->x += delta; +} + +static gboolean +logical_monitor_is_rotated (CcDisplayLogicalMonitor *lm) +{ + switch (lm->rotation) + { + case CC_DISPLAY_ROTATION_90: + case CC_DISPLAY_ROTATION_270: + case CC_DISPLAY_ROTATION_90_FLIPPED: + case CC_DISPLAY_ROTATION_270_FLIPPED: + return TRUE; + default: + return FALSE; + } +} + +static int +logical_monitor_width (CcDisplayLogicalMonitor *lm) +{ + CcDisplayMonitorDBus *monitor; + CcDisplayModeDBus *mode; + GHashTableIter iter; + int width; + + g_hash_table_iter_init (&iter, lm->monitors); + g_hash_table_iter_next (&iter, (void **) &monitor, NULL); + mode = CC_DISPLAY_MODE_DBUS (monitor->current_mode); + if (logical_monitor_is_rotated (lm)) + width = mode ? mode->height : 0; + else + width = mode ? mode->width : 0; + + if (monitor->config->layout_mode == CC_DISPLAY_LAYOUT_MODE_LOGICAL) + return round (width / lm->scale); + else + return width; +} + +static void +add_y_delta (gpointer d1, gpointer d2) +{ + CcDisplayLogicalMonitor *m = d1; + int delta = GPOINTER_TO_INT (d2); + m->y += delta; +} + +static void +cc_display_config_dbus_ensure_non_offset_coords (CcDisplayConfigDBus *self) +{ + GList *x_axis, *y_axis; + CcDisplayLogicalMonitor *m; + + if (g_hash_table_size (self->logical_monitors) == 0) + return; + + x_axis = g_hash_table_get_keys (self->logical_monitors); + x_axis = g_list_sort (x_axis, sort_x_axis); + y_axis = g_hash_table_get_keys (self->logical_monitors); + y_axis = g_list_sort (y_axis, sort_y_axis); + + m = x_axis->data; + if (m->x != 0) + g_list_foreach (x_axis, add_x_delta, GINT_TO_POINTER (- m->x)); + + m = y_axis->data; + if (m->y != 0) + g_list_foreach (y_axis, add_y_delta, GINT_TO_POINTER (- m->y)); + + g_list_free (x_axis); + g_list_free (y_axis); +} + +static void +cc_display_config_dbus_append_right (CcDisplayConfigDBus *self, + CcDisplayLogicalMonitor *monitor) +{ + GList *x_axis; + CcDisplayLogicalMonitor *last; + + if (g_hash_table_size (self->logical_monitors) == 0) + { + monitor->x = 0; + monitor->y = 0; + return; + } + + x_axis = g_hash_table_get_keys (self->logical_monitors); + x_axis = g_list_sort (x_axis, sort_x_axis); + last = g_list_last (x_axis)->data; + monitor->x = last->x + logical_monitor_width (last); + monitor->y = last->y; + + g_list_free (x_axis); +} + +static void +cc_display_config_dbus_make_linear (CcDisplayConfigDBus *self) +{ + CcDisplayLogicalMonitor *primary; + GList *logical_monitors, *l; + int x; + + if (self->primary && self->primary->logical_monitor) + { + primary = self->primary->logical_monitor; + primary->x = primary->y = 0; + x = logical_monitor_width (primary); + } + else + { + primary = NULL; + x = 0; + } + + logical_monitors = g_hash_table_get_keys (self->logical_monitors); + for (l = logical_monitors; l != NULL; l = l->next) + { + CcDisplayLogicalMonitor *m = l->data; + + if (m == primary) + continue; + + m->x = x; + m->y = 0; + x += logical_monitor_width (m); + } + + g_list_free (logical_monitors); +} diff --git a/panels/display/cc-display-config-dbus.h b/panels/display/cc-display-config-dbus.h new file mode 100644 index 0000000..8609087 --- /dev/null +++ b/panels/display/cc-display-config-dbus.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#pragma once + +#include <glib-object.h> + +#include "cc-display-config.h" + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_MODE_DBUS (cc_display_mode_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayModeDBus, cc_display_mode_dbus, + CC, DISPLAY_MODE_DBUS, CcDisplayMode) + +#define CC_TYPE_DISPLAY_MONITOR_DBUS (cc_display_monitor_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayMonitorDBus, cc_display_monitor_dbus, + CC, DISPLAY_MONITOR_DBUS, CcDisplayMonitor) + +#define CC_TYPE_DISPLAY_CONFIG_DBUS (cc_display_config_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayConfigDBus, cc_display_config_dbus, + CC, DISPLAY_CONFIG_DBUS, CcDisplayConfig) + +G_END_DECLS diff --git a/panels/display/cc-display-config-manager-dbus.c b/panels/display/cc-display-config-manager-dbus.c new file mode 100644 index 0000000..678b696 --- /dev/null +++ b/panels/display/cc-display-config-manager-dbus.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "cc-display-config-dbus.h" +#include "cc-display-config-manager-dbus.h" + +#include <gio/gio.h> + +struct _CcDisplayConfigManagerDBus +{ + CcDisplayConfigManager parent_instance; + + GCancellable *cancellable; + GDBusConnection *connection; + guint monitors_changed_id; + + GVariant *current_state; + + gboolean apply_allowed; + gboolean night_light_supported; +}; + +G_DEFINE_TYPE (CcDisplayConfigManagerDBus, + cc_display_config_manager_dbus, + CC_TYPE_DISPLAY_CONFIG_MANAGER) + +static CcDisplayConfig * +cc_display_config_manager_dbus_get_current (CcDisplayConfigManager *pself) +{ + CcDisplayConfigManagerDBus *self = CC_DISPLAY_CONFIG_MANAGER_DBUS (pself); + + if (!self->current_state) + return NULL; + + return g_object_new (CC_TYPE_DISPLAY_CONFIG_DBUS, + "state", self->current_state, + "connection", self->connection, NULL); +} + +static void +got_current_state (GObject *object, + GAsyncResult *result, + gpointer data) +{ + CcDisplayConfigManagerDBus *self; + GVariant *variant; + g_autoptr(GError) error = NULL; + + variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), + result, &error); + if (!variant) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + self = CC_DISPLAY_CONFIG_MANAGER_DBUS (data); + g_clear_pointer (&self->current_state, g_variant_unref); + _cc_display_config_manager_emit_changed (CC_DISPLAY_CONFIG_MANAGER (data)); + g_warning ("Error calling GetCurrentState: %s", error->message); + } + return; + } + + self = CC_DISPLAY_CONFIG_MANAGER_DBUS (data); + g_clear_pointer (&self->current_state, g_variant_unref); + self->current_state = variant; + + _cc_display_config_manager_emit_changed (CC_DISPLAY_CONFIG_MANAGER (self)); +} + +static void +get_current_state (CcDisplayConfigManagerDBus *self) +{ + g_dbus_connection_call (self->connection, + "org.gnome.Mutter.DisplayConfig", + "/org/gnome/Mutter/DisplayConfig", + "org.gnome.Mutter.DisplayConfig", + "GetCurrentState", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + self->cancellable, + got_current_state, + self); +} + +static void +monitors_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer data) +{ + CcDisplayConfigManagerDBus *self = CC_DISPLAY_CONFIG_MANAGER_DBUS (data); + get_current_state (self); +} + +static void +bus_gotten (GObject *object, + GAsyncResult *result, + gpointer data) +{ + CcDisplayConfigManagerDBus *self; + GDBusConnection *connection; + g_autoptr(GError) error = NULL; + g_autoptr(GDBusProxy) proxy = NULL; + g_autoptr(GVariant) variant = NULL; + + connection = g_bus_get_finish (result, &error); + if (!connection) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + _cc_display_config_manager_emit_changed (CC_DISPLAY_CONFIG_MANAGER (data)); + g_warning ("Error obtaining DBus connection: %s", error->message); + } + return; + } + + self = CC_DISPLAY_CONFIG_MANAGER_DBUS (data); + self->connection = connection; + self->monitors_changed_id = + g_dbus_connection_signal_subscribe (self->connection, + "org.gnome.Mutter.DisplayConfig", + "org.gnome.Mutter.DisplayConfig", + "MonitorsChanged", + "/org/gnome/Mutter/DisplayConfig", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + monitors_changed, + self, + NULL); + + proxy = g_dbus_proxy_new_sync (self->connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.gnome.Mutter.DisplayConfig", + "/org/gnome/Mutter/DisplayConfig", + "org.gnome.Mutter.DisplayConfig", + NULL, + &error); + if (!proxy) + { + g_warning ("Failed to create D-Bus proxy to \"org.gnome.Mutter.DisplayConfig\": %s", + error->message); + return; + } + + variant = g_dbus_proxy_get_cached_property (proxy, "ApplyMonitorsConfigAllowed"); + if (variant) + self->apply_allowed = g_variant_get_boolean (variant); + else + g_warning ("Missing property 'ApplyMonitorsConfigAllowed' on DisplayConfig API"); + + variant = g_dbus_proxy_get_cached_property (proxy, "NightLightSupported"); + if (variant) + self->night_light_supported = g_variant_get_boolean (variant); + else + g_warning ("Missing property 'NightLightSupported' on DisplayConfig API"); + + get_current_state (self); +} + +static void +cc_display_config_manager_dbus_init (CcDisplayConfigManagerDBus *self) +{ + self->apply_allowed = TRUE; + self->night_light_supported = TRUE; + self->cancellable = g_cancellable_new (); + g_bus_get (G_BUS_TYPE_SESSION, self->cancellable, bus_gotten, self); +} + +static void +cc_display_config_manager_dbus_finalize (GObject *object) +{ + CcDisplayConfigManagerDBus *self = CC_DISPLAY_CONFIG_MANAGER_DBUS (object); + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + + if (self->monitors_changed_id && self->connection) + g_dbus_connection_signal_unsubscribe (self->connection, + self->monitors_changed_id); + g_clear_object (&self->connection); + g_clear_pointer (&self->current_state, g_variant_unref); + + G_OBJECT_CLASS (cc_display_config_manager_dbus_parent_class)->finalize (object); +} + +static gboolean +cc_display_config_manager_dbus_get_apply_allowed (CcDisplayConfigManager *pself) +{ + CcDisplayConfigManagerDBus *self = CC_DISPLAY_CONFIG_MANAGER_DBUS (pself); + + return self->apply_allowed; +} + +static gboolean +cc_display_config_manager_dbus_get_night_light_supported (CcDisplayConfigManager *pself) +{ + CcDisplayConfigManagerDBus *self = CC_DISPLAY_CONFIG_MANAGER_DBUS (pself); + + return self->night_light_supported; +} + +static void +cc_display_config_manager_dbus_class_init (CcDisplayConfigManagerDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcDisplayConfigManagerClass *parent_class = CC_DISPLAY_CONFIG_MANAGER_CLASS (klass); + + gobject_class->finalize = cc_display_config_manager_dbus_finalize; + + parent_class->get_current = cc_display_config_manager_dbus_get_current; + parent_class->get_apply_allowed = cc_display_config_manager_dbus_get_apply_allowed; + parent_class->get_night_light_supported = cc_display_config_manager_dbus_get_night_light_supported; +} + +CcDisplayConfigManager * +cc_display_config_manager_dbus_new (void) +{ + return g_object_new (CC_TYPE_DISPLAY_CONFIG_MANAGER_DBUS, NULL); +} diff --git a/panels/display/cc-display-config-manager-dbus.h b/panels/display/cc-display-config-manager-dbus.h new file mode 100644 index 0000000..a099598 --- /dev/null +++ b/panels/display/cc-display-config-manager-dbus.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#pragma once + +#include <glib-object.h> + +#include "cc-display-config-manager.h" + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_CONFIG_MANAGER_DBUS (cc_display_config_manager_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayConfigManagerDBus, cc_display_config_manager_dbus, + CC, DISPLAY_CONFIG_MANAGER_DBUS, CcDisplayConfigManager) + +CcDisplayConfigManager * cc_display_config_manager_dbus_new (void); + +G_END_DECLS diff --git a/panels/display/cc-display-config-manager.c b/panels/display/cc-display-config-manager.c new file mode 100644 index 0000000..f231edd --- /dev/null +++ b/panels/display/cc-display-config-manager.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "cc-display-config-manager.h" + +G_DEFINE_TYPE (CcDisplayConfigManager, + cc_display_config_manager, + G_TYPE_OBJECT) + +enum +{ + CONFIG_MANAGER_CHANGED, + N_CONFIG_MANAGER_SIGNALS, +}; + +static guint config_manager_signals[N_CONFIG_MANAGER_SIGNALS] = { 0 }; + +static void +cc_display_config_manager_init (CcDisplayConfigManager *self) +{ +} + +static void +cc_display_config_manager_class_init (CcDisplayConfigManagerClass *klass) +{ + config_manager_signals[CONFIG_MANAGER_CHANGED] = + g_signal_new ("changed", + CC_TYPE_DISPLAY_CONFIG_MANAGER, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +void +_cc_display_config_manager_emit_changed (CcDisplayConfigManager *self) +{ + g_signal_emit (self, config_manager_signals[CONFIG_MANAGER_CHANGED], 0); +} + +CcDisplayConfig * +cc_display_config_manager_get_current (CcDisplayConfigManager *self) +{ + return CC_DISPLAY_CONFIG_MANAGER_GET_CLASS (self)->get_current (self); +} + +gboolean +cc_display_config_manager_get_apply_allowed (CcDisplayConfigManager *self) +{ + return CC_DISPLAY_CONFIG_MANAGER_GET_CLASS (self)->get_apply_allowed (self); +} + +gboolean +cc_display_config_manager_get_night_light_supported (CcDisplayConfigManager *self) +{ + return CC_DISPLAY_CONFIG_MANAGER_GET_CLASS (self)->get_night_light_supported (self); +} diff --git a/panels/display/cc-display-config-manager.h b/panels/display/cc-display-config-manager.h new file mode 100644 index 0000000..ab1e84f --- /dev/null +++ b/panels/display/cc-display-config-manager.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#pragma once + +#include <glib-object.h> + +#include "cc-display-config.h" + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_CONFIG_MANAGER (cc_display_config_manager_get_type ()) +G_DECLARE_DERIVABLE_TYPE (CcDisplayConfigManager, cc_display_config_manager, + CC, DISPLAY_CONFIG_MANAGER, GObject) + +struct _CcDisplayConfigManagerClass +{ + GObjectClass parent_class; + + CcDisplayConfig * (*get_current) (CcDisplayConfigManager *self); + gboolean (* get_apply_allowed) (CcDisplayConfigManager *self); + gboolean (* get_night_light_supported) (CcDisplayConfigManager *self); +}; + +CcDisplayConfig * cc_display_config_manager_get_current (CcDisplayConfigManager *self); + +gboolean cc_display_config_manager_get_apply_allowed (CcDisplayConfigManager *self); + +gboolean cc_display_config_manager_get_night_light_supported (CcDisplayConfigManager *self); + +void _cc_display_config_manager_emit_changed (CcDisplayConfigManager *self); + +G_END_DECLS diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c new file mode 100644 index 0000000..b7532f9 --- /dev/null +++ b/panels/display/cc-display-config.c @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <gio/gio.h> +#include <math.h> +#include "cc-display-config.h" + +static const double known_diagonals[] = { + 12.1, + 13.3, + 15.6 +}; + +static char * +diagonal_to_str (double d) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (known_diagonals); i++) + { + double delta; + + delta = fabs(known_diagonals[i] - d); + if (delta < 0.1) + return g_strdup_printf ("%0.1lf\"", known_diagonals[i]); + } + + return g_strdup_printf ("%d\"", (int) (d + 0.5)); +} + +static char * +make_display_size_string (int width_mm, + int height_mm) +{ + char *inches = NULL; + + if (width_mm > 0 && height_mm > 0) + { + double d = sqrt (width_mm * width_mm + height_mm * height_mm); + + inches = diagonal_to_str (d / 25.4); + } + + return inches; +} + +static char * +make_output_ui_name (CcDisplayMonitor *output) +{ + int width_mm, height_mm; + g_autofree char *size = NULL; + + cc_display_monitor_get_physical_size (output, &width_mm, &height_mm); + size = make_display_size_string (width_mm, height_mm); + if (size) + return g_strdup_printf ("%s (%s)", cc_display_monitor_get_display_name (output), size); + else + return g_strdup_printf ("%s", cc_display_monitor_get_display_name (output)); +} + + + +G_DEFINE_TYPE (CcDisplayMode, + cc_display_mode, + G_TYPE_OBJECT) + +static void +cc_display_mode_init (CcDisplayMode *self) +{ +} + +static void +cc_display_mode_class_init (CcDisplayModeClass *klass) +{ +} + +gboolean +cc_display_mode_is_clone_mode (CcDisplayMode *self) +{ + return CC_DISPLAY_MODE_GET_CLASS (self)->is_clone_mode (self); +} + +void +cc_display_mode_get_resolution (CcDisplayMode *self, int *w, int *h) +{ + return CC_DISPLAY_MODE_GET_CLASS (self)->get_resolution (self, w, h); +} + +GArray * +cc_display_mode_get_supported_scales (CcDisplayMode *self) +{ + return CC_DISPLAY_MODE_GET_CLASS (self)->get_supported_scales (self); +} + +double +cc_display_mode_get_preferred_scale (CcDisplayMode *self) +{ + return CC_DISPLAY_MODE_GET_CLASS (self)->get_preferred_scale (self); +} + +gboolean +cc_display_mode_is_interlaced (CcDisplayMode *self) +{ + return CC_DISPLAY_MODE_GET_CLASS (self)->is_interlaced (self); +} + +gboolean +cc_display_mode_is_preferred (CcDisplayMode *self) +{ + return CC_DISPLAY_MODE_GET_CLASS (self)->is_preferred (self); +} + +int +cc_display_mode_get_freq (CcDisplayMode *self) +{ + return CC_DISPLAY_MODE_GET_CLASS (self)->get_freq (self); +} + +double +cc_display_mode_get_freq_f (CcDisplayMode *self) +{ + return CC_DISPLAY_MODE_GET_CLASS (self)->get_freq_f (self); +} + + +struct _CcDisplayMonitorPrivate { + int ui_number; + gchar *ui_name; + gchar *ui_number_name; + gboolean is_usable; +}; +typedef struct _CcDisplayMonitorPrivate CcDisplayMonitorPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (CcDisplayMonitor, + cc_display_monitor, + G_TYPE_OBJECT) + +static void +cc_display_monitor_init (CcDisplayMonitor *self) +{ + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + priv->ui_number = 0; + priv->ui_name = NULL; + priv->ui_number_name = NULL; + priv->is_usable = TRUE; +} + +static void +cc_display_monitor_finalize (GObject *object) +{ + CcDisplayMonitor *self = CC_DISPLAY_MONITOR (object); + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + g_clear_pointer (&priv->ui_name, g_free); + g_clear_pointer (&priv->ui_number_name, g_free); + + G_OBJECT_CLASS (cc_display_monitor_parent_class)->finalize (object); +} + +static void +cc_display_monitor_class_init (CcDisplayMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = cc_display_monitor_finalize; + + g_signal_new ("rotation", + CC_TYPE_DISPLAY_MONITOR, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + g_signal_new ("mode", + CC_TYPE_DISPLAY_MONITOR, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + g_signal_new ("primary", + CC_TYPE_DISPLAY_MONITOR, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + g_signal_new ("active", + CC_TYPE_DISPLAY_MONITOR, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + g_signal_new ("scale", + CC_TYPE_DISPLAY_MONITOR, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + g_signal_new ("position-changed", + CC_TYPE_DISPLAY_MONITOR, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + g_signal_new ("is-usable", + CC_TYPE_DISPLAY_MONITOR, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +const char * +cc_display_monitor_get_display_name (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_display_name (self); +} + +const char * +cc_display_monitor_get_connector_name (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_connector_name (self); +} + +gboolean +cc_display_monitor_is_builtin (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->is_builtin (self); +} + +gboolean +cc_display_monitor_is_primary (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->is_primary (self); +} + +void +cc_display_monitor_set_primary (CcDisplayMonitor *self, gboolean primary) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_primary (self, primary); +} + +gboolean +cc_display_monitor_is_active (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->is_active (self); +} + +void +cc_display_monitor_set_active (CcDisplayMonitor *self, gboolean active) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_active (self, active); +} + +CcDisplayRotation +cc_display_monitor_get_rotation (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_rotation (self); +} + +void +cc_display_monitor_set_rotation (CcDisplayMonitor *self, + CcDisplayRotation rotation) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_rotation (self, rotation); +} + +gboolean +cc_display_monitor_supports_rotation (CcDisplayMonitor *self, CcDisplayRotation r) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->supports_rotation (self, r); +} + +void +cc_display_monitor_get_physical_size (CcDisplayMonitor *self, int *w, int *h) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_physical_size (self, w, h); +} + +void +cc_display_monitor_get_geometry (CcDisplayMonitor *self, int *x, int *y, int *w, int *h) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_geometry (self, x, y, w, h); +} + +CcDisplayMode * +cc_display_monitor_get_mode (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_mode (self); +} + +CcDisplayMode * +cc_display_monitor_get_preferred_mode (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_preferred_mode (self); +} + +guint32 +cc_display_monitor_get_id (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_id (self); +} + +GList * +cc_display_monitor_get_modes (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_modes (self); +} + +gboolean +cc_display_monitor_supports_underscanning (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->supports_underscanning (self); +} + +gboolean +cc_display_monitor_get_underscanning (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_underscanning (self); +} + +void +cc_display_monitor_set_underscanning (CcDisplayMonitor *self, + gboolean underscanning) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_underscanning (self, underscanning); +} + +CcDisplayMonitorPrivacy +cc_display_monitor_get_privacy (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_privacy (self); +} + +void +cc_display_monitor_set_mode (CcDisplayMonitor *self, CcDisplayMode *m) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_mode (self, m); +} + +void +cc_display_monitor_set_compatible_clone_mode (CcDisplayMonitor *self, CcDisplayMode *m) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_mode (self, m); +} + +void +cc_display_monitor_set_position (CcDisplayMonitor *self, int x, int y) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_position (self, x, y); +} + +double +cc_display_monitor_get_scale (CcDisplayMonitor *self) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->get_scale (self); +} + +void +cc_display_monitor_set_scale (CcDisplayMonitor *self, double s) +{ + return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_scale (self, s); +} + +gboolean +cc_display_monitor_is_useful (CcDisplayMonitor *self) +{ + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + return priv->is_usable && + cc_display_monitor_is_active (self); +} + +gboolean +cc_display_monitor_is_usable (CcDisplayMonitor *self) +{ + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + return priv->is_usable; +} + +void +cc_display_monitor_set_usable (CcDisplayMonitor *self, gboolean is_usable) +{ + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + priv->is_usable = is_usable; + + g_signal_emit_by_name (self, "is-usable"); +} + +gint +cc_display_monitor_get_ui_number (CcDisplayMonitor *self) +{ + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + return priv->ui_number; +} + +const char * +cc_display_monitor_get_ui_name (CcDisplayMonitor *self) +{ + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + return priv->ui_name; +} + +const char * +cc_display_monitor_get_ui_number_name (CcDisplayMonitor *self) +{ + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + return priv->ui_number_name; +} + +char * +cc_display_monitor_dup_ui_number_name (CcDisplayMonitor *self) +{ + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + return g_strdup (priv->ui_number_name); +} + +static void +cc_display_monitor_set_ui_info (CcDisplayMonitor *self, gint ui_number, gchar *ui_name) +{ + + CcDisplayMonitorPrivate *priv = cc_display_monitor_get_instance_private (self); + + priv->ui_number = ui_number; + g_free (priv->ui_name); + priv->ui_name = ui_name; + priv->ui_number_name = g_strdup_printf ("%d\u2003%s", ui_number, ui_name); +} + +struct _CcDisplayConfigPrivate { + GList *ui_sorted_monitors; +}; +typedef struct _CcDisplayConfigPrivate CcDisplayConfigPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (CcDisplayConfig, + cc_display_config, + G_TYPE_OBJECT) + +static void +cc_display_config_init (CcDisplayConfig *self) +{ + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + + priv->ui_sorted_monitors = NULL; +} + +static void +cc_display_config_constructed (GObject *object) +{ + CcDisplayConfig *self = CC_DISPLAY_CONFIG (object); + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + GList *monitors = cc_display_config_get_monitors (self); + GList *item; + gint ui_number = 1; + + for (item = monitors; item != NULL; item = item->next) + { + CcDisplayMonitor *monitor = item->data; + + if (cc_display_monitor_is_builtin (monitor)) + priv->ui_sorted_monitors = g_list_prepend (priv->ui_sorted_monitors, monitor); + else + priv->ui_sorted_monitors = g_list_append (priv->ui_sorted_monitors, monitor); + } + + for (item = priv->ui_sorted_monitors; item != NULL; item = item->next) + { + CcDisplayMonitor *monitor = item->data; + char *ui_name; + ui_name = make_output_ui_name (monitor); + + cc_display_monitor_set_ui_info (monitor, ui_number, ui_name); + + ui_number += 1; + } +} + +static void +cc_display_config_finalize (GObject *object) +{ + CcDisplayConfig *self = CC_DISPLAY_CONFIG (object); + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + + g_list_free (priv->ui_sorted_monitors); + + G_OBJECT_CLASS (cc_display_config_parent_class)->finalize (object); +} + +static void +cc_display_config_class_init (CcDisplayConfigClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_signal_new ("primary", + CC_TYPE_DISPLAY_CONFIG, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + g_signal_new ("panel-orientation-managed", + CC_TYPE_DISPLAY_CONFIG, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + gobject_class->constructed = cc_display_config_constructed; + gobject_class->finalize = cc_display_config_finalize; +} + +GList * +cc_display_config_get_monitors (CcDisplayConfig *self) +{ + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), NULL); + return CC_DISPLAY_CONFIG_GET_CLASS (self)->get_monitors (self); +} + +GList * +cc_display_config_get_ui_sorted_monitors (CcDisplayConfig *self) +{ + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), NULL); + return priv->ui_sorted_monitors; +} + +int +cc_display_config_count_useful_monitors (CcDisplayConfig *self) +{ + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + GList *outputs, *l; + guint count = 0; + + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), 0); + + outputs = priv->ui_sorted_monitors; + for (l = outputs; l != NULL; l = l->next) + { + CcDisplayMonitor *output = l->data; + if (!cc_display_monitor_is_useful (output)) + continue; + else + count++; + } + return count; + +} + +gboolean +cc_display_config_is_applicable (CcDisplayConfig *self) +{ + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), FALSE); + return CC_DISPLAY_CONFIG_GET_CLASS (self)->is_applicable (self); +} + +void +cc_display_config_set_mode_on_all_outputs (CcDisplayConfig *config, + CcDisplayMode *clone_mode) +{ + GList *outputs, *l; + + g_return_if_fail (CC_IS_DISPLAY_CONFIG (config)); + g_return_if_fail (cc_display_mode_is_clone_mode (clone_mode)); + + outputs = cc_display_config_get_monitors (config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + cc_display_monitor_set_compatible_clone_mode (output, clone_mode); + cc_display_monitor_set_position (output, 0, 0); + } +} + +gboolean +cc_display_config_equal (CcDisplayConfig *self, + CcDisplayConfig *other) +{ + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), FALSE); + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (other), FALSE); + + return CC_DISPLAY_CONFIG_GET_CLASS (self)->equal (self, other); +} + +gboolean +cc_display_config_apply (CcDisplayConfig *self, + GError **error) +{ + if (!CC_IS_DISPLAY_CONFIG (self)) + { + g_warning ("Cannot apply invalid configuration"); + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Cannot apply invalid configuration"); + return FALSE; + } + + return CC_DISPLAY_CONFIG_GET_CLASS (self)->apply (self, error); +} + +gboolean +cc_display_config_is_cloning (CcDisplayConfig *self) +{ + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), FALSE); + return CC_DISPLAY_CONFIG_GET_CLASS (self)->is_cloning (self); +} + +void +cc_display_config_set_cloning (CcDisplayConfig *self, + gboolean clone) +{ + g_return_if_fail (CC_IS_DISPLAY_CONFIG (self)); + return CC_DISPLAY_CONFIG_GET_CLASS (self)->set_cloning (self, clone); +} + +GList * +cc_display_config_generate_cloning_modes (CcDisplayConfig *self) +{ + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), NULL); + return CC_DISPLAY_CONFIG_GET_CLASS (self)->generate_cloning_modes (self); +} + +gboolean +cc_display_config_is_layout_logical (CcDisplayConfig *self) +{ + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), FALSE); + return CC_DISPLAY_CONFIG_GET_CLASS (self)->is_layout_logical (self); +} + +void +cc_display_config_set_minimum_size (CcDisplayConfig *self, + int width, + int height) +{ + g_return_if_fail (CC_IS_DISPLAY_CONFIG (self)); + CC_DISPLAY_CONFIG_GET_CLASS (self)->set_minimum_size (self, width, height); +} + +gboolean +cc_display_config_is_scaled_mode_valid (CcDisplayConfig *self, + CcDisplayMode *mode, + double scale) +{ + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), FALSE); + g_return_val_if_fail (CC_IS_DISPLAY_MODE (mode), FALSE); + return CC_DISPLAY_CONFIG_GET_CLASS (self)->is_scaled_mode_valid (self, mode, scale); +} + +gboolean +cc_display_config_get_panel_orientation_managed (CcDisplayConfig *self) +{ + return CC_DISPLAY_CONFIG_GET_CLASS (self)->get_panel_orientation_managed (self); +} diff --git a/panels/display/cc-display-config.h b/panels/display/cc-display-config.h new file mode 100644 index 0000000..1241adc --- /dev/null +++ b/panels/display/cc-display-config.h @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#pragma once + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* + * GNOME Control Center display configuration system: + * + * The display configuration system consists of multiple concepts: + * + * CcDisplayConfig: + * + * Configuration instance, read from mutter using the + * org.gnome.Mutter.DisplayConfig D-Bus API. Contains information about the + * current configuration. Can be copied, to create a representation of a + * configuration at a given time, and applied, applying any changes that has + * been made to the objects associated with the configuration. + * + * CcDisplayConfig provides a list of all known "monitors" known to the + * compositor. It does not know about ports without any monitors connected, + * nor low level details about monitors, such as tiling etc. + * + * CcDisplayMonitor: + * + * A high level representation of a connected monitor. A monitor have details + * associated with it, some which can be altered. Each CcDisplayMonitor + * instance is associated with a single CcDisplayConfig instance. All + * alteration to a monitor is cached and not applied until + * cc_display_config_apply() is called on the corresponding CcDisplayConfig + * object. + * + * CcDisplayMode: + * + * A monitor mode, including resolution, refresh rate, and scale. Each monitor + * will have a list of possible modes. + * + */ + +typedef enum _CcDisplayRotation +{ + CC_DISPLAY_ROTATION_NONE, + CC_DISPLAY_ROTATION_90, + CC_DISPLAY_ROTATION_180, + CC_DISPLAY_ROTATION_270, + CC_DISPLAY_ROTATION_FLIPPED, + CC_DISPLAY_ROTATION_90_FLIPPED, + CC_DISPLAY_ROTATION_180_FLIPPED, + CC_DISPLAY_ROTATION_270_FLIPPED, +} CcDisplayRotation; + +typedef enum _CcDisplayMonitorPrivacy +{ + CC_DISPLAY_MONITOR_PRIVACY_UNSUPPORTED = 0, + CC_DISPLAY_MONITOR_PRIVACY_DISABLED = 1 << 0, + CC_DISPLAY_MONITOR_PRIVACY_ENABLED = 1 << 1, + CC_DISPLAY_MONITOR_PRIVACY_LOCKED = 1 << 2, +} CcDisplayMonitorPrivacy; + + +#define CC_TYPE_DISPLAY_MODE (cc_display_mode_get_type ()) +G_DECLARE_DERIVABLE_TYPE (CcDisplayMode, cc_display_mode, + CC, DISPLAY_MODE, GObject) + +struct _CcDisplayModeClass +{ + GObjectClass parent_class; + + gboolean (*is_clone_mode) (CcDisplayMode *self); + void (*get_resolution) (CcDisplayMode *self, int *w, int *h); + GArray* (*get_supported_scales) (CcDisplayMode *self); + double (*get_preferred_scale) (CcDisplayMode *self); + gboolean (*is_interlaced) (CcDisplayMode *self); + gboolean (*is_preferred) (CcDisplayMode *self); + int (*get_freq) (CcDisplayMode *self); + double (*get_freq_f) (CcDisplayMode *self); +}; + + +#define CC_TYPE_DISPLAY_MONITOR (cc_display_monitor_get_type ()) +G_DECLARE_DERIVABLE_TYPE (CcDisplayMonitor, cc_display_monitor, + CC, DISPLAY_MONITOR, GObject) + +struct _CcDisplayMonitorClass +{ + GObjectClass parent_class; + + guint32 (*get_id) (CcDisplayMonitor *self); + const char* (*get_display_name) (CcDisplayMonitor *self); + const char* (*get_connector_name) (CcDisplayMonitor *self); + gboolean (*is_builtin) (CcDisplayMonitor *self); + gboolean (*is_primary) (CcDisplayMonitor *self); + void (*set_primary) (CcDisplayMonitor *self, + gboolean primary); + gboolean (*is_active) (CcDisplayMonitor *self); + void (*set_active) (CcDisplayMonitor *self, + gboolean a); + CcDisplayRotation (*get_rotation) (CcDisplayMonitor *self); + void (*set_rotation) (CcDisplayMonitor *self, + CcDisplayRotation r); + gboolean (*supports_rotation) (CcDisplayMonitor *self, + CcDisplayRotation r); + void (*get_physical_size) (CcDisplayMonitor *self, + int *w, + int *h); + void (*get_geometry) (CcDisplayMonitor *self, + int *x, + int *y, + int *w, + int *h); + gboolean (*supports_underscanning) (CcDisplayMonitor *self); + gboolean (*get_underscanning) (CcDisplayMonitor *self); + void (*set_underscanning) (CcDisplayMonitor *self, + gboolean u); + CcDisplayMonitorPrivacy (*get_privacy) (CcDisplayMonitor *self); + CcDisplayMode* (*get_mode) (CcDisplayMonitor *self); + CcDisplayMode* (*get_preferred_mode) (CcDisplayMonitor *self); + GList* (*get_modes) (CcDisplayMonitor *self); + void (*set_compatible_clone_mode) (CcDisplayMonitor *self, + CcDisplayMode *m); + void (*set_mode) (CcDisplayMonitor *self, + CcDisplayMode *m); + void (*set_position) (CcDisplayMonitor *self, + int x, + int y); + double (*get_scale) (CcDisplayMonitor *self); + void (*set_scale) (CcDisplayMonitor *self, + double s); +}; + + +#define CC_TYPE_DISPLAY_CONFIG (cc_display_config_get_type ()) +G_DECLARE_DERIVABLE_TYPE (CcDisplayConfig, cc_display_config, + CC, DISPLAY_CONFIG, GObject) + +struct _CcDisplayConfigClass +{ + GObjectClass parent_class; + + GList* (*get_monitors) (CcDisplayConfig *self); + gboolean (*is_applicable) (CcDisplayConfig *self); + gboolean (*equal) (CcDisplayConfig *self, + CcDisplayConfig *other); + gboolean (*apply) (CcDisplayConfig *self, + GError **error); + gboolean (*is_cloning) (CcDisplayConfig *self); + void (*set_cloning) (CcDisplayConfig *self, + gboolean clone); + GList* (*generate_cloning_modes) (CcDisplayConfig *self); + gboolean (*is_layout_logical) (CcDisplayConfig *self); + void (*set_minimum_size) (CcDisplayConfig *self, + int width, + int height); + gboolean (*is_scaled_mode_valid) (CcDisplayConfig *self, + CcDisplayMode *mode, + double scale); + gboolean (* get_panel_orientation_managed) (CcDisplayConfig *self); +}; + + +GList* cc_display_config_get_monitors (CcDisplayConfig *config); +GList* cc_display_config_get_ui_sorted_monitors (CcDisplayConfig *config); +int cc_display_config_count_useful_monitors (CcDisplayConfig *config); +gboolean cc_display_config_is_applicable (CcDisplayConfig *config); +gboolean cc_display_config_equal (CcDisplayConfig *config, + CcDisplayConfig *other); +gboolean cc_display_config_apply (CcDisplayConfig *config, + GError **error); +gboolean cc_display_config_is_cloning (CcDisplayConfig *config); +void cc_display_config_set_cloning (CcDisplayConfig *config, + gboolean clone); +GList* cc_display_config_generate_cloning_modes (CcDisplayConfig *config); + +void cc_display_config_set_mode_on_all_outputs (CcDisplayConfig *config, + CcDisplayMode *mode); + +gboolean cc_display_config_is_layout_logical (CcDisplayConfig *self); +void cc_display_config_set_minimum_size (CcDisplayConfig *self, + int width, + int height); +gboolean cc_display_config_is_scaled_mode_valid (CcDisplayConfig *self, + CcDisplayMode *mode, + double scale); +gboolean cc_display_config_get_panel_orientation_managed + (CcDisplayConfig *self); + +const char* cc_display_monitor_get_display_name (CcDisplayMonitor *monitor); +gboolean cc_display_monitor_is_active (CcDisplayMonitor *monitor); +void cc_display_monitor_set_active (CcDisplayMonitor *monitor, + gboolean active); +const char* cc_display_monitor_get_connector_name (CcDisplayMonitor *monitor); +CcDisplayRotation cc_display_monitor_get_rotation (CcDisplayMonitor *monitor); +void cc_display_monitor_set_rotation (CcDisplayMonitor *monitor, + CcDisplayRotation r); +gboolean cc_display_monitor_supports_rotation (CcDisplayMonitor *monitor, + CcDisplayRotation rotation); +void cc_display_monitor_get_physical_size (CcDisplayMonitor *monitor, + int *w, + int *h); +gboolean cc_display_monitor_is_builtin (CcDisplayMonitor *monitor); +gboolean cc_display_monitor_is_primary (CcDisplayMonitor *monitor); +void cc_display_monitor_set_primary (CcDisplayMonitor *monitor, + gboolean primary); +guint32 cc_display_monitor_get_id (CcDisplayMonitor *monitor); + +gboolean cc_display_monitor_supports_underscanning (CcDisplayMonitor *monitor); +gboolean cc_display_monitor_get_underscanning (CcDisplayMonitor *monitor); +void cc_display_monitor_set_underscanning (CcDisplayMonitor *monitor, + gboolean underscanning); + +CcDisplayMonitorPrivacy cc_display_monitor_get_privacy (CcDisplayMonitor *self); + +CcDisplayMode* cc_display_monitor_get_mode (CcDisplayMonitor *monitor); +void cc_display_monitor_get_geometry (CcDisplayMonitor *monitor, + int *x, + int *y, + int *width, + int *height); +GList* cc_display_monitor_get_modes (CcDisplayMonitor *monitor); +CcDisplayMode* cc_display_monitor_get_preferred_mode (CcDisplayMonitor *monitor); +double cc_display_monitor_get_scale (CcDisplayMonitor *monitor); +void cc_display_monitor_set_scale (CcDisplayMonitor *monitor, + double s); + +void cc_display_monitor_set_compatible_clone_mode (CcDisplayMonitor *monitor, + CcDisplayMode *mode); +void cc_display_monitor_set_mode (CcDisplayMonitor *monitor, + CcDisplayMode *mode); +void cc_display_monitor_set_position (CcDisplayMonitor *monitor, + int x, + int y); + +gboolean cc_display_monitor_is_useful (CcDisplayMonitor *monitor); +gboolean cc_display_monitor_is_usable (CcDisplayMonitor *monitor); +void cc_display_monitor_set_usable (CcDisplayMonitor *monitor, + gboolean is_usable); +int cc_display_monitor_get_ui_number (CcDisplayMonitor *monitor); +const char* cc_display_monitor_get_ui_name (CcDisplayMonitor *monitor); +const char* cc_display_monitor_get_ui_number_name (CcDisplayMonitor *monitor); +char* cc_display_monitor_dup_ui_number_name (CcDisplayMonitor *monitor); + +gboolean cc_display_mode_is_clone_mode (CcDisplayMode *mode); +void cc_display_mode_get_resolution (CcDisplayMode *mode, + int *width, + int *height); +GArray* cc_display_mode_get_supported_scales (CcDisplayMode *self); +double cc_display_mode_get_preferred_scale (CcDisplayMode *self); +gboolean cc_display_mode_is_interlaced (CcDisplayMode *mode); +gboolean cc_display_mode_is_preferred (CcDisplayMode *mode); +int cc_display_mode_get_freq (CcDisplayMode *mode); +double cc_display_mode_get_freq_f (CcDisplayMode *mode); + +G_END_DECLS diff --git a/panels/display/cc-display-panel.c b/panels/display/cc-display-panel.c new file mode 100644 index 0000000..6958e19 --- /dev/null +++ b/panels/display/cc-display-panel.c @@ -0,0 +1,1140 @@ +/* + * Copyright (C) 2007, 2008 Red Hat, Inc. + * Copyright (C) 2013 Intel, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "cc-display-panel.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <stdlib.h> +#include <gdesktop-enums.h> +#include <math.h> + +#include "shell/cc-object-storage.h" +#include <libupower-glib/upower.h> + +#include "cc-display-config-manager-dbus.h" +#include "cc-display-config.h" +#include "cc-display-arrangement.h" +#include "cc-night-light-page.h" +#include "cc-display-resources.h" +#include "cc-display-settings.h" + +/* The minimum supported size for the panel + * Note that WIDTH is assumed to be the larger size and we accept portrait + * mode too effectively (in principle we should probably restrict the rotation + * setting in that case). */ +#define MINIMUM_WIDTH 720 +#define MINIMUM_HEIGHT 360 + +#define PANEL_PADDING 32 +#define SECTION_PADDING 32 +#define HEADING_PADDING 12 + +#define DISPLAY_SCHEMA "org.gnome.settings-daemon.plugins.color" + +typedef enum { + CC_DISPLAY_CONFIG_JOIN, + CC_DISPLAY_CONFIG_CLONE, + + CC_DISPLAY_CONFIG_INVALID_NONE, +} CcDisplayConfigType; + +#define CC_DISPLAY_CONFIG_LAST_VALID CC_DISPLAY_CONFIG_CLONE + +struct _CcDisplayPanel +{ + CcPanel parent_instance; + + CcDisplayConfigManager *manager; + CcDisplayConfig *current_config; + CcDisplayMonitor *current_output; + + gint rebuilding_counter; + + CcDisplayArrangement *arrangement; + CcDisplaySettings *settings; + + guint focus_id; + + CcNightLightPage *night_light_page; + GtkLabel *night_light_state_label; + + UpClient *up_client; + gboolean lid_is_closed; + + GDBusProxy *shell_proxy; + + GtkWidget *apply_titlebar; + GtkWidget *apply_button; + GtkWidget *cancel_button; + AdwWindowTitle *apply_titlebar_title_widget; + + GListStore *primary_display_list; + GList *monitor_rows; + + GtkWidget *display_settings_disabled_group; + + GtkWidget *arrangement_row; + AdwBin *arrangement_bin; + GtkToggleButton *config_type_join; + GtkToggleButton *config_type_mirror; + GtkWidget *display_multiple_displays; + AdwBin *display_settings_bin; + GtkWidget *display_settings_group; + AdwWindowTitle *display_settings_title_widget; + AdwLeaflet *leaflet; + AdwComboRow *primary_display_row; + AdwPreferencesGroup *single_display_settings_group; + + GtkShortcutController *toplevel_shortcuts; + GtkShortcut *escape_shortcut; + + GSettings *display_settings; +}; + +CC_PANEL_REGISTER (CcDisplayPanel, cc_display_panel) + +static void +update_apply_button (CcDisplayPanel *panel); +static void +apply_current_configuration (CcDisplayPanel *self); +static void +reset_current_config (CcDisplayPanel *panel); +static void +rebuild_ui (CcDisplayPanel *panel); +static void +set_current_output (CcDisplayPanel *panel, + CcDisplayMonitor *output, + gboolean force); +static void +on_screen_changed (CcDisplayPanel *panel); + + +static CcDisplayConfigType +config_get_current_type (CcDisplayPanel *panel) +{ + guint n_active_outputs; + GList *outputs, *l; + + outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); + n_active_outputs = 0; + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + if (cc_display_monitor_is_useful (output)) + n_active_outputs += 1; + } + + if (n_active_outputs == 0) + return CC_DISPLAY_CONFIG_INVALID_NONE; + + if (cc_display_config_is_cloning (panel->current_config)) + return CC_DISPLAY_CONFIG_CLONE; + + return CC_DISPLAY_CONFIG_JOIN; +} + +static CcDisplayConfigType +cc_panel_get_selected_type (CcDisplayPanel *panel) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_join))) + return CC_DISPLAY_CONFIG_JOIN; + else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_mirror))) + return CC_DISPLAY_CONFIG_CLONE; + else + g_assert_not_reached (); +} + +static CcDisplayMode * +find_preferred_mode (GList *modes) +{ + GList *l; + + for (l = modes; l; l = l->next) + { + CcDisplayMode *mode = l->data; + + if (cc_display_mode_is_preferred (mode)) + return mode; + } + + return NULL; +} + +static void +config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type) +{ + CcDisplayConfigType current_type = config_get_current_type (panel); + GList *outputs, *l; + CcDisplayMonitor *current_primary = NULL; + gdouble old_primary_scale = -1; + + /* Do not do anything if the current detected configuration type is + * identitcal to what we expect. */ + if (type == current_type) + return; + + reset_current_config (panel); + + outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + if (cc_display_monitor_is_primary (output)) + { + current_primary = output; + old_primary_scale = cc_display_monitor_get_scale (current_primary); + break; + } + } + + switch (type) + { + case CC_DISPLAY_CONFIG_JOIN: + g_debug ("Creating new join config"); + /* Enable all usable outputs + * Note that this might result in invalid configurations as we + * we might not be able to drive all attached monitors. */ + cc_display_config_set_cloning (panel->current_config, FALSE); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + gdouble scale; + CcDisplayMode *mode; + + mode = cc_display_monitor_get_preferred_mode (output); + /* If the monitor was active, try using the current scale, otherwise + * try picking the preferred scale. */ + if (cc_display_monitor_is_active (output)) + scale = cc_display_monitor_get_scale (output); + else + scale = cc_display_mode_get_preferred_scale (mode); + + /* If we cannot use the current/preferred scale, try to fall back to + * the previously scale of the primary monitor instead. + * This is not guaranteed to result in a valid configuration! */ + if (!cc_display_config_is_scaled_mode_valid (panel->current_config, + mode, + scale)) + { + if (current_primary && + cc_display_config_is_scaled_mode_valid (panel->current_config, + mode, + old_primary_scale)) + scale = old_primary_scale; + } + + cc_display_monitor_set_active (output, cc_display_monitor_is_usable (output)); + cc_display_monitor_set_mode (output, mode); + cc_display_monitor_set_scale (output, scale); + } + break; + + case CC_DISPLAY_CONFIG_CLONE: + { + g_debug ("Creating new clone config"); + gdouble scale; + g_autolist(CcDisplayMode) modes = NULL; + CcDisplayMode *clone_mode; + + /* Turn on cloning and select the best mode we can find by default */ + cc_display_config_set_cloning (panel->current_config, TRUE); + + modes = cc_display_config_generate_cloning_modes (panel->current_config); + clone_mode = find_preferred_mode (modes); + g_return_if_fail (clone_mode); + + /* Take the preferred scale by default, */ + scale = cc_display_mode_get_preferred_scale (clone_mode); + /* but prefer the old primary scale if that is valid. */ + if (current_primary && + cc_display_config_is_scaled_mode_valid (panel->current_config, + clone_mode, + old_primary_scale)) + scale = old_primary_scale; + + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + cc_display_monitor_set_compatible_clone_mode (output, clone_mode); + cc_display_monitor_set_scale (output, scale); + } + } + break; + + default: + g_assert_not_reached (); + } + + if (!panel->rebuilding_counter) + rebuild_ui (panel); +} + +static void +cc_panel_set_selected_type (CcDisplayPanel *panel, CcDisplayConfigType type) +{ + switch (type) + { + case CC_DISPLAY_CONFIG_JOIN: + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_join), TRUE); + break; + case CC_DISPLAY_CONFIG_CLONE: + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_mirror), TRUE); + break; + default: + g_assert_not_reached (); + } + + config_ensure_of_type (panel, type); +} + +static void +monitor_labeler_hide (CcDisplayPanel *self) +{ + if (!self->shell_proxy) + return; + + g_dbus_proxy_call (self->shell_proxy, + "HideMonitorLabels", + NULL, G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +static void +monitor_labeler_show (CcDisplayPanel *self) +{ + GList *outputs, *l; + GVariantBuilder builder; + gint number = 0; + + if (!self->shell_proxy || !self->current_config) + return; + + outputs = cc_display_config_get_ui_sorted_monitors (self->current_config); + if (!outputs) + return; + + if (cc_display_config_is_cloning (self->current_config)) + return monitor_labeler_hide (self); + + g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); + g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY); + + for (l = outputs; l != NULL; l = l->next) + { + CcDisplayMonitor *output = l->data; + + number = cc_display_monitor_get_ui_number (output); + if (number == 0) + continue; + + g_variant_builder_add (&builder, "{sv}", + cc_display_monitor_get_connector_name (output), + g_variant_new_int32 (number)); + } + + g_variant_builder_close (&builder); + + if (number < 2) + { + g_variant_builder_clear (&builder); + return monitor_labeler_hide (self); + } + + g_dbus_proxy_call (self->shell_proxy, + "ShowMonitorLabels", + g_variant_builder_end (&builder), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +static void +ensure_monitor_labels (CcDisplayPanel *self) +{ + g_autoptr(GList) windows = NULL; + GList *w; + + windows = gtk_window_list_toplevels (); + + for (w = windows; w; w = w->next) + { + if (gtk_window_is_active (GTK_WINDOW (w->data))) + { + monitor_labeler_show (self); + break; + } + } + + if (!w) + monitor_labeler_hide (self); +} + +static void +dialog_toplevel_is_active_changed (CcDisplayPanel *self) +{ + ensure_monitor_labels (self); +} + +static void +reset_titlebar (CcDisplayPanel *self) +{ + gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->toplevel_shortcuts), + GTK_PHASE_NONE); + gtk_widget_hide (self->apply_titlebar); +} + +static void +active_panel_changed (CcPanel *self) +{ + CcShell *shell; + g_autoptr(CcPanel) panel = NULL; + + shell = cc_panel_get_shell (CC_PANEL (self)); + g_object_get (shell, "active-panel", &panel, NULL); + if (panel != self) + reset_titlebar (CC_DISPLAY_PANEL (self)); +} + +static void +cc_display_panel_dispose (GObject *object) +{ + CcDisplayPanel *self = CC_DISPLAY_PANEL (object); + GtkWidget *toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self))); + + reset_titlebar (CC_DISPLAY_PANEL (object)); + + if (self->focus_id) + { + self->focus_id = 0; + monitor_labeler_hide (CC_DISPLAY_PANEL (object)); + } + + g_clear_pointer (&self->monitor_rows, g_list_free); + g_clear_object (&self->manager); + g_clear_object (&self->current_config); + g_clear_object (&self->up_client); + + g_clear_object (&self->shell_proxy); + + g_signal_handlers_disconnect_by_data (toplevel, self); + + G_OBJECT_CLASS (cc_display_panel_parent_class)->dispose (object); +} + +static void +on_arrangement_selected_ouptut_changed_cb (CcDisplayPanel *panel) +{ + set_current_output (panel, cc_display_arrangement_get_selected_output (panel->arrangement), FALSE); +} + +static void +on_monitor_settings_updated_cb (CcDisplayPanel *panel, + CcDisplayMonitor *monitor, + CcDisplaySettings *settings) +{ + if (monitor) + cc_display_config_snap_output (panel->current_config, monitor); + update_apply_button (panel); +} + +static void +on_back_button_clicked_cb (GtkButton *button, + CcDisplayPanel *self) +{ + adw_leaflet_set_visible_child_name (self->leaflet, "displays"); +} + +static void +on_config_type_toggled_cb (CcDisplayPanel *panel, + GtkCheckButton *btn) +{ + CcDisplayConfigType type; + + if (panel->rebuilding_counter > 0) + return; + + if (!panel->current_config) + return; + + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn))) + return; + + type = cc_panel_get_selected_type (panel); + config_ensure_of_type (panel, type); +} + +static void +on_night_light_enabled_changed_cb (GSettings *settings, + const gchar *key, + CcDisplayPanel *self) +{ + if (g_settings_get_boolean (self->display_settings, "night-light-enabled")) + gtk_label_set_label (self->night_light_state_label, _("On")); + else + gtk_label_set_label (self->night_light_state_label, _("Off")); +} + +static void +on_night_light_row_activated_cb (GtkListBoxRow *row, + CcDisplayPanel *self) +{ + adw_leaflet_set_visible_child_name (self->leaflet, "night-light"); +} + +static void +on_primary_display_selected_item_changed_cb (CcDisplayPanel *panel) +{ + gint idx = adw_combo_row_get_selected (panel->primary_display_row); + g_autoptr(CcDisplayMonitor) output = NULL; + + if (idx < 0 || panel->rebuilding_counter > 0) + return; + + output = g_list_model_get_item (G_LIST_MODEL (panel->primary_display_list), idx); + + if (cc_display_monitor_is_primary (output)) + return; + + cc_display_monitor_set_primary (output, TRUE); + update_apply_button (panel); +} + +static void +on_toplevel_folded (CcDisplayPanel *panel, GParamSpec *pspec, GtkWidget *toplevel) +{ + gboolean folded; + + g_object_get (toplevel, "folded", &folded, NULL); + cc_display_settings_refresh_layout (panel->settings, folded); +} + +static gboolean +on_toplevel_escape_pressed_cb (GtkWidget *widget, + GVariant *args, + CcDisplayPanel *self) +{ + if (gtk_widget_get_visible (self->apply_titlebar)) + { + gtk_widget_activate (self->cancel_button); + return GDK_EVENT_STOP; + } + + return GDK_EVENT_PROPAGATE; +} + +static void +cc_display_panel_constructed (GObject *object) +{ + CcShell *shell = cc_panel_get_shell (CC_PANEL (object)); + GtkWidget *toplevel = cc_shell_get_toplevel (shell); + + g_signal_connect_object (cc_panel_get_shell (CC_PANEL (object)), "notify::active-panel", + G_CALLBACK (active_panel_changed), object, G_CONNECT_SWAPPED); + + g_signal_connect_swapped (toplevel, "notify::folded", G_CALLBACK (on_toplevel_folded), object); + on_toplevel_folded (CC_DISPLAY_PANEL (object), NULL, toplevel); + + G_OBJECT_CLASS (cc_display_panel_parent_class)->constructed (object); +} + +static const char * +cc_display_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/prefs-display"; +} + +static void +cc_display_panel_class_init (CcDisplayPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + g_type_ensure (CC_TYPE_NIGHT_LIGHT_PAGE); + + panel_class->get_help_uri = cc_display_panel_get_help_uri; + + object_class->constructed = cc_display_panel_constructed; + object_class->dispose = cc_display_panel_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-display-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, apply_button); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, apply_titlebar); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, apply_titlebar_title_widget); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_disabled_group); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_bin); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, cancel_button); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_multiple_displays); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_join); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_mirror); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_bin); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_group); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_title_widget); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, escape_shortcut); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, leaflet); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, night_light_page); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, night_light_state_label); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, primary_display_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, single_display_settings_group); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, toplevel_shortcuts); + + gtk_widget_class_bind_template_callback (widget_class, apply_current_configuration); + gtk_widget_class_bind_template_callback (widget_class, on_back_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_config_type_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, on_night_light_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, on_primary_display_selected_item_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_screen_changed); + gtk_widget_class_bind_template_callback (widget_class, on_toplevel_escape_pressed_cb); +} + +static void +set_current_output (CcDisplayPanel *panel, + CcDisplayMonitor *output, + gboolean force) +{ + gboolean changed; + + /* Note, this function is also called if the internal UI needs updating after a rebuild. */ + changed = (output != panel->current_output); + + if (!changed && !force) + return; + + panel->rebuilding_counter++; + + panel->current_output = output; + + if (changed) + { + cc_display_settings_set_selected_output (panel->settings, panel->current_output); + cc_display_arrangement_set_selected_output (panel->arrangement, panel->current_output); + + adw_window_title_set_title (panel->display_settings_title_widget, + output ? cc_display_monitor_get_ui_name (output) : ""); + } + + panel->rebuilding_counter--; +} + +static void +on_monitor_row_activated_cb (GtkListBoxRow *row, + CcDisplayPanel *self) +{ + CcDisplayMonitor *monitor; + + monitor = g_object_get_data (G_OBJECT (row), "monitor"); + set_current_output (self, monitor, FALSE); + + adw_leaflet_set_visible_child_name (self->leaflet, "display-settings"); +} + +static void +add_display_row (CcDisplayPanel *self, + CcDisplayMonitor *monitor) +{ + g_autofree gchar *number_string = NULL; + GtkWidget *number_label; + GtkWidget *icon; + GtkWidget *row; + + row = adw_action_row_new (); + g_object_set_data (G_OBJECT (row), "monitor", monitor); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), + cc_display_monitor_get_ui_name (monitor)); + + number_string = g_strdup_printf ("%d", cc_display_monitor_get_ui_number (monitor)); + number_label = gtk_label_new (number_string); + gtk_widget_set_valign (number_label, GTK_ALIGN_CENTER); + gtk_widget_set_halign (number_label, GTK_ALIGN_CENTER); + gtk_widget_add_css_class (number_label, "monitor-label"); + adw_action_row_add_prefix (ADW_ACTION_ROW (row), number_label); + + icon = gtk_image_new_from_icon_name ("go-next-symbolic"); + adw_action_row_add_suffix (ADW_ACTION_ROW (row), icon); + adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), icon); + + adw_preferences_group_add (ADW_PREFERENCES_GROUP (self->display_settings_group), row); + + g_signal_connect (row, "activated", G_CALLBACK (on_monitor_row_activated_cb), self); + + self->monitor_rows = g_list_prepend (self->monitor_rows, row); +} + +static void +move_display_settings_to_main_page (CcDisplayPanel *self) +{ + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self->settings)); + + if (parent != GTK_WIDGET (self->display_settings_bin)) + return; + + g_object_ref (self->settings); + adw_bin_set_child (self->display_settings_bin, NULL); + adw_preferences_group_add (self->single_display_settings_group, + GTK_WIDGET (self->settings)); + g_object_unref (self->settings); + + gtk_widget_show (GTK_WIDGET (self->single_display_settings_group)); +} + +static void +move_display_settings_to_separate_page (CcDisplayPanel *self) +{ + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self->settings)); + + if (parent == GTK_WIDGET (self->display_settings_bin)) + return; + + g_object_ref (self->settings); + adw_preferences_group_remove (self->single_display_settings_group, + GTK_WIDGET (self->settings)); + adw_bin_set_child (self->display_settings_bin, + GTK_WIDGET (self->settings)); + g_object_unref (self->settings); + + gtk_widget_hide (GTK_WIDGET (self->single_display_settings_group)); +} + +static void +rebuild_ui (CcDisplayPanel *panel) +{ + guint n_outputs, n_active_outputs, n_usable_outputs; + GList *outputs, *l; + CcDisplayConfigType type; + + if (!cc_display_config_manager_get_apply_allowed (panel->manager)) + { + gtk_widget_set_visible (panel->display_settings_disabled_group, TRUE); + gtk_widget_set_visible (panel->display_settings_group, FALSE); + gtk_widget_set_visible (panel->arrangement_row, FALSE); + return; + } + + panel->rebuilding_counter++; + + g_list_store_remove_all (panel->primary_display_list); + + /* Remove all monitor rows */ + while (panel->monitor_rows) + { + adw_preferences_group_remove (ADW_PREFERENCES_GROUP (panel->display_settings_group), + panel->monitor_rows->data); + panel->monitor_rows = g_list_remove_link (panel->monitor_rows, panel->monitor_rows); + } + + if (!panel->current_config) + { + panel->rebuilding_counter--; + return; + } + + gtk_widget_set_visible (panel->display_settings_disabled_group, FALSE); + + n_active_outputs = 0; + n_usable_outputs = 0; + outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + if (!cc_display_monitor_is_usable (output)) + continue; + + n_usable_outputs += 1; + + if (cc_display_monitor_is_active (output)) + { + n_active_outputs += 1; + + g_list_store_append (panel->primary_display_list, output); + if (cc_display_monitor_is_primary (output)) + adw_combo_row_set_selected (panel->primary_display_row, + g_list_model_get_n_items (G_LIST_MODEL (panel->primary_display_list)) - 1); + + /* Ensure that an output is selected; note that this doesn't ensure + * the selected output is any useful (i.e. when switching types). + */ + if (!panel->current_output) + set_current_output (panel, output, FALSE); + } + + add_display_row (panel, l->data); + } + + /* Sync the rebuild lists/buttons */ + set_current_output (panel, panel->current_output, TRUE); + + n_outputs = g_list_length (outputs); + type = config_get_current_type (panel); + + if (n_outputs == 2 && n_usable_outputs == 2) + { + /* We only show the top chooser with two monitors that are + * both usable (i.e. two monitors incl. internal and lid not closed). + * In this case, the arrangement widget is shown if we are in JOIN mode. + */ + if (type > CC_DISPLAY_CONFIG_LAST_VALID) + type = CC_DISPLAY_CONFIG_JOIN; + + gtk_widget_set_visible (panel->display_settings_group, type == CC_DISPLAY_CONFIG_JOIN); + gtk_widget_set_visible (panel->display_multiple_displays, TRUE); + gtk_widget_set_visible (panel->arrangement_row, type == CC_DISPLAY_CONFIG_JOIN); + + if (type == CC_DISPLAY_CONFIG_CLONE) + move_display_settings_to_main_page (panel); + else + move_display_settings_to_separate_page (panel); + } + else if (n_usable_outputs > 1) + { + /* We have more than one usable monitor. In this case there is no chooser, + * and we always show the arrangement widget. + */ + gtk_widget_set_visible (panel->display_settings_group, TRUE); + gtk_widget_set_visible (panel->display_multiple_displays, FALSE); + gtk_widget_set_visible (panel->arrangement_row, TRUE); + + /* Mirror is also invalid as it cannot be configured using this UI. */ + if (type == CC_DISPLAY_CONFIG_CLONE || type > CC_DISPLAY_CONFIG_LAST_VALID) + type = CC_DISPLAY_CONFIG_JOIN; + + move_display_settings_to_separate_page (panel); + } + else + { + type = CC_DISPLAY_CONFIG_JOIN; + + gtk_widget_set_visible (panel->display_settings_group, FALSE); + gtk_widget_set_visible (panel->display_multiple_displays, FALSE); + gtk_widget_set_visible (panel->arrangement_row, FALSE); + + move_display_settings_to_main_page (panel); + } + + cc_display_settings_set_multimonitor (panel->settings, + n_outputs > 1 && + type != CC_DISPLAY_CONFIG_CLONE); + + cc_panel_set_selected_type (panel, type); + + panel->rebuilding_counter--; + update_apply_button (panel); +} + +static void +update_panel_orientation_managed (CcDisplayPanel *panel, + gboolean managed) +{ + cc_display_settings_set_has_accelerometer (panel->settings, managed); +} + +static void +reset_current_config (CcDisplayPanel *panel) +{ + CcDisplayConfig *current; + CcDisplayConfig *old; + GList *outputs, *l; + + g_debug ("Resetting current config!"); + + /* We need to hold on to the config until all display references are dropped. */ + old = panel->current_config; + panel->current_output = NULL; + + current = cc_display_config_manager_get_current (panel->manager); + + if (!current) + return; + + cc_display_config_set_minimum_size (current, MINIMUM_WIDTH, MINIMUM_HEIGHT); + panel->current_config = current; + + g_signal_connect_object (current, "panel-orientation-managed", + G_CALLBACK (update_panel_orientation_managed), panel, + G_CONNECT_SWAPPED); + update_panel_orientation_managed (panel, + cc_display_config_get_panel_orientation_managed (current)); + + g_list_store_remove_all (panel->primary_display_list); + + if (panel->current_config) + { + outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + /* Mark any builtin monitor as unusable if the lid is closed. */ + if (cc_display_monitor_is_builtin (output) && panel->lid_is_closed) + cc_display_monitor_set_usable (output, FALSE); + } + } + + cc_display_arrangement_set_config (panel->arrangement, panel->current_config); + cc_display_settings_set_config (panel->settings, panel->current_config); + set_current_output (panel, NULL, FALSE); + + g_clear_object (&old); + + update_apply_button (panel); +} + +static void +on_screen_changed (CcDisplayPanel *panel) +{ + if (!panel->manager) + return; + + reset_titlebar (panel); + + reset_current_config (panel); + rebuild_ui (panel); + + if (!panel->current_config) + return; + + ensure_monitor_labels (panel); +} + +static void +show_apply_titlebar (CcDisplayPanel *panel, gboolean is_applicable) +{ + gtk_widget_show (panel->apply_titlebar); + gtk_widget_set_sensitive (panel->apply_button, is_applicable); + + if (is_applicable) + { + adw_window_title_set_title (panel->apply_titlebar_title_widget, + _("Apply Changes?")); + } + else + { + adw_window_title_set_title (panel->apply_titlebar_title_widget, + _("Changes Cannot be Applied")); + adw_window_title_set_subtitle (panel->apply_titlebar_title_widget, + _("This could be due to hardware limitations.")); + } + + gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (panel->toplevel_shortcuts), + GTK_PHASE_BUBBLE); +} + +static void +update_apply_button (CcDisplayPanel *panel) +{ + gboolean config_equal; + g_autoptr(CcDisplayConfig) applied_config = NULL; + + if (!panel->current_config) + { + reset_titlebar (panel); + return; + } + + applied_config = cc_display_config_manager_get_current (panel->manager); + + config_equal = cc_display_config_equal (panel->current_config, + applied_config); + + if (config_equal) + reset_titlebar (panel); + else + show_apply_titlebar (panel, cc_display_config_is_applicable (panel->current_config)); +} + +static void +apply_current_configuration (CcDisplayPanel *self) +{ + g_autoptr(GError) error = NULL; + + cc_display_config_apply (self->current_config, &error); + + /* re-read the configuration */ + on_screen_changed (self); + + if (error) + g_warning ("Error applying configuration: %s", error->message); + + adw_leaflet_set_visible_child_name (self->leaflet, "displays"); +} + +static void +mapped_cb (CcDisplayPanel *panel) +{ + CcShell *shell; + GtkWidget *toplevel; + + shell = cc_panel_get_shell (CC_PANEL (panel)); + toplevel = cc_shell_get_toplevel (shell); + if (toplevel && !panel->focus_id) + panel->focus_id = g_signal_connect_object (toplevel, "notify::is-active", + G_CALLBACK (dialog_toplevel_is_active_changed), panel, G_CONNECT_SWAPPED); +} + +static void +cc_display_panel_up_client_changed (CcDisplayPanel *self) +{ + gboolean lid_is_closed; + + lid_is_closed = up_client_get_lid_is_closed (self->up_client); + + if (lid_is_closed != self->lid_is_closed) + { + self->lid_is_closed = lid_is_closed; + + on_screen_changed (self); + } +} + +static void +shell_proxy_ready (GObject *source, + GAsyncResult *res, + CcDisplayPanel *self) +{ + GDBusProxy *proxy; + g_autoptr(GError) error = NULL; + + proxy = cc_object_storage_create_dbus_proxy_finish (res, &error); + if (!proxy) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to contact gnome-shell: %s", error->message); + return; + } + + self->shell_proxy = proxy; + + ensure_monitor_labels (self); +} + +static void +session_bus_ready (GObject *source, + GAsyncResult *res, + CcDisplayPanel *self) +{ + GDBusConnection *bus; + g_autoptr(GError) error = NULL; + + bus = g_bus_get_finish (res, &error); + if (!bus) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_warning ("Failed to get session bus: %s", error->message); + } + return; + } + + self->manager = cc_display_config_manager_dbus_new (); + g_signal_connect_object (self->manager, "changed", + G_CALLBACK (on_screen_changed), + self, + G_CONNECT_SWAPPED); +} + +static void +cc_display_panel_init (CcDisplayPanel *self) +{ + g_autoptr(GtkCssProvider) provider = NULL; + GtkExpression *expression; + + g_resources_register (cc_display_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->arrangement = cc_display_arrangement_new (NULL); + gtk_widget_set_size_request (GTK_WIDGET (self->arrangement), 375, 175); + adw_bin_set_child (self->arrangement_bin, GTK_WIDGET (self->arrangement)); + + g_signal_connect_object (self->arrangement, "updated", + G_CALLBACK (update_apply_button), self, + G_CONNECT_SWAPPED); + g_signal_connect_object (self->arrangement, "notify::selected-output", + G_CALLBACK (on_arrangement_selected_ouptut_changed_cb), self, + G_CONNECT_SWAPPED); + + self->settings = cc_display_settings_new (); + adw_bin_set_child (self->display_settings_bin, GTK_WIDGET (self->settings)); + g_signal_connect_object (self->settings, "updated", + G_CALLBACK (on_monitor_settings_updated_cb), self, + G_CONNECT_SWAPPED); + + self->primary_display_list = g_list_store_new (CC_TYPE_DISPLAY_MONITOR); + + expression = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, 0, NULL, + G_CALLBACK (cc_display_monitor_dup_ui_number_name), + self, NULL); + adw_combo_row_set_expression (self->primary_display_row, expression); + adw_combo_row_set_model (self->primary_display_row, + G_LIST_MODEL (self->primary_display_list)); + + self->up_client = up_client_new (); + if (up_client_get_lid_is_present (self->up_client)) + { + g_signal_connect_object (self->up_client, "notify::lid-is-closed", + G_CALLBACK (cc_display_panel_up_client_changed), self, G_CONNECT_SWAPPED); + cc_display_panel_up_client_changed (self); + } + else + g_clear_object (&self->up_client); + + g_signal_connect (self, "map", G_CALLBACK (mapped_cb), NULL); + + cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + "org.gnome.Shell", + "/org/gnome/Shell", + "org.gnome.Shell", + cc_panel_get_cancellable (CC_PANEL (self)), + (GAsyncReadyCallback) shell_proxy_ready, + self); + + g_bus_get (G_BUS_TYPE_SESSION, + cc_panel_get_cancellable (CC_PANEL (self)), + (GAsyncReadyCallback) session_bus_ready, + self); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/display/display-arrangement.css"); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + gtk_shortcut_set_action (self->escape_shortcut, + gtk_callback_action_new ((GtkShortcutFunc) on_toplevel_escape_pressed_cb, + self, + NULL)); + + self->display_settings = g_settings_new (DISPLAY_SCHEMA); + g_signal_connect_object (self->display_settings, + "changed::night-light-enabled", + G_CALLBACK (on_night_light_enabled_changed_cb), + self, + 0); + on_night_light_enabled_changed_cb (NULL, NULL, self); +} diff --git a/panels/display/cc-display-panel.h b/panels/display/cc-display-panel.h new file mode 100644 index 0000000..a7ade28 --- /dev/null +++ b/panels/display/cc-display-panel.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 Intel, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Thomas Wood <thomas.wood@intel.com> + * + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_PANEL (cc_display_panel_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayPanel, cc_display_panel, CC, DISPLAY_PANEL, CcPanel) + +G_END_DECLS diff --git a/panels/display/cc-display-panel.ui b/panels/display/cc-display-panel.ui new file mode 100644 index 0000000..9337b18 --- /dev/null +++ b/panels/display/cc-display-panel.ui @@ -0,0 +1,316 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcDisplayPanel" parent="CcPanel"> + + <child> + <object class="GtkShortcutController" id="toplevel_shortcuts"> + <property name="scope">global</property> + <property name="name">Display Panel Globals Shortcuts</property> + <child> + <object class="GtkShortcut" id="escape_shortcut"> + <property name="trigger">Escape</property> + <property name="action">callback(on_toplevel_escape_pressed_cb)</property> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkOverlay"> + + <child type="overlay"> + <object class="AdwHeaderBar" id="apply_titlebar"> + <property name="visible">False</property> + <property name="valign">start</property> + <property name="show-start-title-buttons">False</property> + <property name="show-end-title-buttons">False</property> + + <child type="start"> + <object class="GtkButton" id="cancel_button"> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Cancel</property> + <signal name="clicked" handler="on_screen_changed" object="CcDisplayPanel" swapped="yes" /> + </object> + </child> + + <property name="title-widget"> + <object class="AdwWindowTitle" id="apply_titlebar_title_widget" /> + </property> + + <child type="end"> + <object class="GtkButton" id="apply_button"> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Apply</property> + <signal name="clicked" handler="apply_current_configuration" object="CcDisplayPanel" swapped="yes" /> + <style> + <class name="suggested-action" /> + </style> + </object> + </child> + + </object> + </child> + + <child> + <object class="AdwLeaflet" id="leaflet"> + <property name="can-unfold">False</property> + + <!-- Displays page --> + <child> + <object class="AdwLeafletPage"> + <property name="name">displays</property> + <property name="child"> + <object class="GtkBox"> + <property name="orientation">vertical</property> + + <!-- Displays page titlebar --> + <child> + <object class="AdwHeaderBar" id="displays_titlebar"> + <property name="show-end-title-buttons">True</property> + <property name="show-start-title-buttons" bind-source="CcDisplayPanel" bind-property="folded" bind-flags="default|sync-create" /> + <child type="start"> + <object class="GtkButton"> + <property name="visible" bind-source="CcDisplayPanel" bind-property="folded" bind-flags="default|sync-create" /> + <property name="icon-name">go-previous-symbolic</property> + <property name="action-name">window.navigate</property> + <property name="action-target">0</property> <!-- 0: ADW_NAVIGATION_DIRECTION_BACK --> + <accessibility> + <property name="label" translatable="yes">Back</property> + </accessibility> + </object> + </child> + <property name="title-widget"> + <object class="AdwWindowTitle"> + <property name="title" bind-source="CcDisplayPanel" bind-property="title" bind-flags="default|sync-create" /> + </object> + </property> + </object> + </child> + + <child> + <object class="AdwPreferencesPage"> + + <child> + <object class="AdwPreferencesGroup" id="display_settings_disabled_group"> + <property name="visible">False</property> + <child> + <object class="AdwStatusPage"> + <property name="vexpand">True</property> + <property name="icon-name">computer-symbolic</property> + <property name="title" translatable="yes">Display Settings Disabled</property> + </object> + </child> + </object> + </child> + + <child> + <object class="AdwPreferencesGroup" id="display_multiple_displays"> + <!-- Config Type --> + <child> + <object class="AdwActionRow" id="config_type_switcher_row"> + <property name="title" translatable="yes">Multiple Displays</property> + <child type="suffix"> + <object class="GtkBox"> + <property name="valign">center</property> + <style> + <class name="linked" /> + </style> + <child> + <object class="GtkToggleButton" id="config_type_join"> + <property name="label" translatable="yes" comments="'Join' as in 'Join displays'">Join</property> + <property name="active">True</property> + <signal name="toggled" handler="on_config_type_toggled_cb" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkToggleButton" id="config_type_mirror"> + <property name="label" translatable="yes">Mirror</property> + <property name="group">config_type_join</property> + <signal name="toggled" handler="on_config_type_toggled_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="AdwPreferencesGroup" id="display_settings_group"> + <!-- Display Arrangement --> + <child> + <object class="AdwPreferencesRow" id="arrangement_row"> + <property name="activatable">False</property> + <child> + <object class="AdwBin" id="arrangement_bin"> + </object> + </child> + </object> + </child> + + <!-- Primary Display --> + <child> + <object class="AdwComboRow" id="primary_display_row"> + <property name="subtitle" translatable="yes">Contains top bar and Activities</property> + <property name="title" translatable="yes">Primary Display</property> + <signal name="notify::selected-item" handler="on_primary_display_selected_item_changed_cb" swapped="yes"/> + </object> + </child> + </object> + + </child> + + <!-- Single Display Settings --> + <child> + <object class="AdwPreferencesGroup" id="single_display_settings_group"> + <property name="visible">False</property> + </object> + </child> + + <!-- Night Light --> + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="AdwActionRow"> + <property name="activatable">True</property> + <property name="title" translatable="yes" comments="This is the redshift functionality where we suppress blue light when the sun has gone down">Night Light</property> + <signal name="activated" handler="on_night_light_row_activated_cb" object="CcDisplayPanel" swapped="no" /> + + <child type="suffix"> + <object class="GtkLabel" id="night_light_state_label"> + <property name="label">On</property> + </object> + </child> + + <child type="suffix"> + <object class="GtkImage"> + <property name="icon-name">go-next-symbolic</property> + </object> + </child> + + </object> + </child> + </object> + </child> + + </object> + </child> + </object> + </property> + </object> + </child> + + <!-- Night Light page --> + <child> + <object class="AdwLeafletPage"> + <property name="name">night-light</property> + <property name="child"> + <object class="GtkBox"> + <property name="orientation">vertical</property> + + <!-- Night Light titlebar --> + <child> + <object class="AdwHeaderBar" id="night_light_titlebar"> + <property name="show-start-title-buttons">True</property> + <property name="show-end-title-buttons">True</property> + <child type="start"> + <object class="GtkButton"> + <property name="icon-name">go-previous-symbolic</property> + <accessibility> + <property name="label" translatable="yes">Back</property> + </accessibility> + <signal name="clicked" handler="on_back_button_clicked_cb" object="CcDisplayPanel" swapped="no" /> + </object> + </child> + <property name="title-widget"> + <object class="AdwWindowTitle"> + <property name="title" translatable="yes">Night Light</property> + </object> + </property> + </object> + </child> + + <!-- Night Light --> + <child> + <object class="CcNightLightPage" id="night_light_page" /> + </child> + + </object> + </property> + </object> + </child> + + <!-- Display Settings page --> + <child> + <object class="AdwLeafletPage"> + <property name="name">display-settings</property> + <property name="child"><object class="GtkBox"> + <property name="orientation">vertical</property> + + <!-- Display Settings titlebar --> + <child> + <object class="AdwHeaderBar" id="display_settings_titlebar"> + <property name="show-start-title-buttons">True</property> + <property name="show-end-title-buttons">True</property> + <child type="start"> + <object class="GtkButton"> + <property name="icon-name">go-previous-symbolic</property> + <accessibility> + <property name="label" translatable="yes">Back</property> + </accessibility> + <signal name="clicked" handler="on_back_button_clicked_cb" object="CcDisplayPanel" swapped="no" /> + </object> + </child> + <property name="title-widget"> + <object class="AdwWindowTitle" id="display_settings_title_widget" /> + </property> + </object> + </child> + + <!-- Display Settings --> + <child> + <object class="AdwPreferencesPage"> + <child> + <object class="AdwPreferencesGroup"> + <child> + <object class="AdwBin" id="display_settings_bin" /> + </child> + </object> + </child> + </object> + </child> + + </object> + </property> + </object> + </child> + + </object> + </child> + + </object> + </child> + + </template> + + <object class="GtkSizeGroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="apply_button" /> + <widget name="cancel_button" /> + </widgets> + </object> + + <object class="GtkSizeGroup"> + <property name="mode">vertical</property> + <widgets> + <widget name="apply_titlebar" /> + <widget name="displays_titlebar" /> + <widget name="display_settings_titlebar" /> + <widget name="night_light_titlebar" /> + </widgets> + </object> + +</interface> + diff --git a/panels/display/cc-display-settings.c b/panels/display/cc-display-settings.c new file mode 100644 index 0000000..8fec65b --- /dev/null +++ b/panels/display/cc-display-settings.c @@ -0,0 +1,919 @@ +/* cc-display-settings.c + * + * Copyright (C) 2007, 2008, 2018, 2019 Red Hat, Inc. + * Copyright (C) 2013 Intel, Inc. + * + * Written by: Benjamin Berg <bberg@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <float.h> +#include <glib/gi18n.h> +#include <float.h> +#include <math.h> +#include "cc-display-settings.h" +#include "cc-display-config.h" + +#define MAX_SCALE_BUTTONS 5 + +struct _CcDisplaySettings +{ + GtkBox object; + + gboolean updating; + gboolean num_scales; + gboolean folded; + guint idle_udpate_id; + + gboolean has_accelerometer; + CcDisplayConfig *config; + CcDisplayMonitor *selected_output; + + GListModel *orientation_list; + GListStore *refresh_rate_list; + GListStore *resolution_list; + GListModel *scale_list; + + GtkWidget *enabled_listbox; + AdwActionRow *enabled_row; + GtkSwitch *enabled_switch; + GtkWidget *orientation_row; + GtkWidget *refresh_rate_row; + GtkWidget *resolution_row; + GtkWidget *scale_bbox; + GtkWidget *scale_buttons_row; + GtkWidget *scale_combo_row; + GtkWidget *underscanning_row; + GtkWidget *underscanning_switch; +}; + +typedef struct _CcDisplaySettings CcDisplaySettings; + +enum { + PROP_0, + PROP_HAS_ACCELEROMETER, + PROP_CONFIG, + PROP_SELECTED_OUTPUT, + PROP_LAST +}; + +G_DEFINE_TYPE (CcDisplaySettings, cc_display_settings, GTK_TYPE_BOX) + +static GParamSpec *props[PROP_LAST]; + +static void on_scale_btn_active_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self); + + +static gboolean +should_show_rotation (CcDisplaySettings *self) +{ + gboolean supports_rotation; + + supports_rotation = cc_display_monitor_supports_rotation (self->selected_output, + CC_DISPLAY_ROTATION_90 | + CC_DISPLAY_ROTATION_180 | + CC_DISPLAY_ROTATION_270); + + /* Doesn't support rotation at all */ + if (!supports_rotation) + return FALSE; + + /* We can always rotate displays that aren't builtin */ + if (!cc_display_monitor_is_builtin (self->selected_output)) + return TRUE; + + /* Only offer rotation if there's no accelerometer */ + return !self->has_accelerometer; +} + +static const gchar * +string_for_rotation (CcDisplayRotation rotation) +{ + switch (rotation) + { + case CC_DISPLAY_ROTATION_NONE: + case CC_DISPLAY_ROTATION_180_FLIPPED: + return C_("Display rotation", "Landscape"); + case CC_DISPLAY_ROTATION_90: + case CC_DISPLAY_ROTATION_270_FLIPPED: + return C_("Display rotation", "Portrait Right"); + case CC_DISPLAY_ROTATION_270: + case CC_DISPLAY_ROTATION_90_FLIPPED: + return C_("Display rotation", "Portrait Left"); + case CC_DISPLAY_ROTATION_180: + case CC_DISPLAY_ROTATION_FLIPPED: + return C_("Display rotation", "Landscape (flipped)"); + } + return ""; +} + +static const gchar * +make_aspect_string (gint width, + gint height) +{ + int ratio; + const gchar *aspect = NULL; + + /* We use a number of Unicode characters below: + * ∶ is U+2236 RATIO + *   is U+2009 THIN SPACE, + * × is U+00D7 MULTIPLICATION SIGN + */ + if (width && height) { + if (width > height) + ratio = width * 10 / height; + else + ratio = height * 10 / width; + + switch (ratio) { + case 13: + aspect = "4∶3"; + break; + case 16: + aspect = "16∶10"; + break; + case 17: + aspect = "16∶9"; + break; + case 23: + aspect = "21∶9"; + break; + case 35: + aspect = "32∶9"; + break; + case 12: + aspect = "5∶4"; + break; + /* This catches 1.5625 as well (1600x1024) when maybe it shouldn't. */ + case 15: + aspect = "3∶2"; + break; + case 18: + aspect = "9∶5"; + break; + case 10: + aspect = "1∶1"; + break; + } + } + + return aspect; +} + +static gchar * +make_refresh_rate_string (CcDisplayMode *mode) +{ + return g_strdup_printf (_("%.2lf Hz"), cc_display_mode_get_freq_f (mode)); +} + +static gchar * +make_resolution_string (CcDisplayMode *mode) +{ + const char *interlaced; + const char *aspect; + int width, height; + + cc_display_mode_get_resolution (mode, &width, &height); + aspect = make_aspect_string (width, height); + interlaced = cc_display_mode_is_interlaced (mode) ? "i" : ""; + + if (aspect != NULL) + return g_strdup_printf ("%d × %d%s (%s)", width, height, interlaced, aspect); + else + return g_strdup_printf ("%d × %d%s", width, height, interlaced); +} + +static double +round_scale_for_ui (double scale) +{ + /* Keep in sync with mutter */ + return round (scale*4)/4; +} + +static gchar * +make_scale_string (gdouble scale) +{ + return g_strdup_printf ("%d %%", (int) (round_scale_for_ui (scale)*100)); +} + +static gint +sort_modes_by_area_desc (CcDisplayMode *a, CcDisplayMode *b) +{ + gint wa, ha, wb, hb; + gint res; + + cc_display_mode_get_resolution (a, &wa, &ha); + cc_display_mode_get_resolution (b, &wb, &hb); + + /* Sort first by width, then height. + * We used to sort by area, but that can be confusing. */ + res = wb - wa; + if (res) + return res; + return hb - ha; +} + +static gint +sort_modes_by_freq_desc (CcDisplayMode *a, CcDisplayMode *b) +{ + double delta = (cc_display_mode_get_freq_f (b) - cc_display_mode_get_freq_f (a))*1000.; + return delta; +} + +static gboolean +cc_display_settings_rebuild_ui (CcDisplaySettings *self) +{ + GtkWidget *child; + g_autolist(CcDisplayMode) clone_modes = NULL; + GList *modes; + GList *item; + gint width, height; + CcDisplayMode *current_mode; + GtkToggleButton *group = NULL; + g_autoptr(GArray) scales = NULL; + gint i; + + self->idle_udpate_id = 0; + + if (!self->config || !self->selected_output) + { + gtk_widget_set_visible (self->enabled_listbox, FALSE); + gtk_widget_set_visible (self->orientation_row, FALSE); + gtk_widget_set_visible (self->refresh_rate_row, FALSE); + gtk_widget_set_visible (self->resolution_row, FALSE); + gtk_widget_set_visible (self->scale_combo_row, FALSE); + gtk_widget_set_visible (self->scale_buttons_row, FALSE); + gtk_widget_set_visible (self->underscanning_row, FALSE); + + return G_SOURCE_REMOVE; + } + + g_object_freeze_notify ((GObject*) self->enabled_switch); + g_object_freeze_notify ((GObject*) self->orientation_row); + g_object_freeze_notify ((GObject*) self->refresh_rate_row); + g_object_freeze_notify ((GObject*) self->resolution_row); + g_object_freeze_notify ((GObject*) self->scale_combo_row); + g_object_freeze_notify ((GObject*) self->underscanning_switch); + + cc_display_monitor_get_geometry (self->selected_output, NULL, NULL, &width, &height); + + /* Selecte the first mode we can find if the monitor is disabled. */ + current_mode = cc_display_monitor_get_mode (self->selected_output); + if (current_mode == NULL) + current_mode = cc_display_monitor_get_preferred_mode (self->selected_output); + if (current_mode == NULL) { + modes = cc_display_monitor_get_modes (self->selected_output); + /* Lets assume that a monitor always has at least one mode. */ + g_assert (modes); + current_mode = CC_DISPLAY_MODE (modes->data); + } + + /* Enabled Switch */ + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->enabled_row), + cc_display_monitor_get_ui_name (self->selected_output)); + gtk_switch_set_active (GTK_SWITCH (self->enabled_switch), + cc_display_monitor_is_active (self->selected_output)); + + if (should_show_rotation (self)) + { + guint i; + CcDisplayRotation rotations[] = { CC_DISPLAY_ROTATION_NONE, + CC_DISPLAY_ROTATION_90, + CC_DISPLAY_ROTATION_270, + CC_DISPLAY_ROTATION_180 }; + + gtk_widget_set_visible (self->orientation_row, TRUE); + + gtk_string_list_splice (GTK_STRING_LIST (self->orientation_list), + 0, + g_list_model_get_n_items (self->orientation_list), + NULL); + for (i = 0; i < G_N_ELEMENTS (rotations); i++) + { + g_autoptr(GObject) obj = NULL; + + if (!cc_display_monitor_supports_rotation (self->selected_output, rotations[i])) + continue; + + gtk_string_list_append (GTK_STRING_LIST (self->orientation_list), + string_for_rotation (rotations[i])); + obj = g_list_model_get_item (self->orientation_list, i); + g_object_set_data (G_OBJECT (obj), "rotation-value", GINT_TO_POINTER (rotations[i])); + + if (cc_display_monitor_get_rotation (self->selected_output) == rotations[i]) + adw_combo_row_set_selected (ADW_COMBO_ROW (self->orientation_row), + g_list_model_get_n_items (G_LIST_MODEL (self->orientation_list)) - 1); + } + } + else + { + gtk_widget_set_visible (self->orientation_row, FALSE); + } + + /* Only show refresh rate if we are not in cloning mode. */ + if (!cc_display_config_is_cloning (self->config)) + { + GList *item; + gdouble freq; + + freq = cc_display_mode_get_freq_f (current_mode); + + modes = cc_display_monitor_get_modes (self->selected_output); + + g_list_store_remove_all (self->refresh_rate_list); + + for (item = modes; item != NULL; item = item->next) + { + gint w, h; + guint new; + CcDisplayMode *mode = CC_DISPLAY_MODE (item->data); + + cc_display_mode_get_resolution (mode, &w, &h); + if (w != width || h != height) + continue; + + /* At some point we used to filter very close resolutions, + * but we don't anymore these days. + */ + new = g_list_store_insert_sorted (self->refresh_rate_list, + mode, + (GCompareDataFunc) sort_modes_by_freq_desc, + NULL); + if (freq == cc_display_mode_get_freq_f (mode)) + adw_combo_row_set_selected (ADW_COMBO_ROW (self->refresh_rate_row), new); + } + + gtk_widget_set_visible (self->refresh_rate_row, TRUE); + } + else + { + gtk_widget_set_visible (self->refresh_rate_row, FALSE); + } + + + /* Resolutions are always shown. */ + gtk_widget_set_visible (self->resolution_row, TRUE); + if (cc_display_config_is_cloning (self->config)) + { + clone_modes = cc_display_config_generate_cloning_modes (self->config); + modes = clone_modes; + } + else + { + modes = cc_display_monitor_get_modes (self->selected_output); + } + + g_list_store_remove_all (self->resolution_list); + g_list_store_append (self->resolution_list, current_mode); + adw_combo_row_set_selected (ADW_COMBO_ROW (self->resolution_row), 0); + for (item = modes; item != NULL; item = item->next) + { + gint ins; + gint w, h; + CcDisplayMode *mode = CC_DISPLAY_MODE (item->data); + + cc_display_mode_get_resolution (mode, &w, &h); + + /* Find the appropriate insertion point. */ + for (ins = 0; ins < g_list_model_get_n_items (G_LIST_MODEL (self->resolution_list)); ins++) + { + g_autoptr(CcDisplayMode) m = NULL; + gint cmp; + + m = g_list_model_get_item (G_LIST_MODEL (self->resolution_list), ins); + + cmp = sort_modes_by_area_desc (mode, m); + /* Next item is smaller, insert at this point. */ + if (cmp < 0) + break; + + /* Don't insert if it is already in the list */ + if (cmp == 0) + { + ins = -1; + break; + } + } + + if (ins >= 0) + g_list_store_insert (self->resolution_list, ins, mode); + } + + + /* Scale row is usually shown. */ + while ((child = gtk_widget_get_first_child (self->scale_bbox)) != NULL) + gtk_box_remove (GTK_BOX (self->scale_bbox), child); + + gtk_string_list_splice (GTK_STRING_LIST (self->scale_list), + 0, + g_list_model_get_n_items (self->scale_list), + NULL); + scales = cc_display_mode_get_supported_scales (current_mode); + self->num_scales = scales->len; + for (i = 0; i < scales->len; i++) + { + g_autofree gchar *scale_str = NULL; + g_autoptr(GObject) value_object = NULL; + double scale = g_array_index (scales, double, i); + GtkWidget *scale_btn; + gboolean is_selected; + + /* ComboRow */ + scale_str = make_scale_string (scale); + is_selected = G_APPROX_VALUE (cc_display_monitor_get_scale (self->selected_output), + scale, DBL_EPSILON); + + gtk_string_list_append (GTK_STRING_LIST (self->scale_list), scale_str); + value_object = g_list_model_get_item (self->scale_list, i); + g_object_set_data_full (G_OBJECT (value_object), "scale", + g_memdup2 (&scale, sizeof (double)), g_free); + if (is_selected) + adw_combo_row_set_selected (ADW_COMBO_ROW (self->scale_combo_row), + g_list_model_get_n_items (G_LIST_MODEL (self->scale_list)) - 1); + + /* ButtonBox */ + scale_btn = gtk_toggle_button_new_with_label (scale_str); + gtk_toggle_button_set_group (GTK_TOGGLE_BUTTON (scale_btn), group); + g_object_set_data_full (G_OBJECT (scale_btn), "scale", + g_memdup2 (&scale, sizeof (double)), g_free); + + if (!group) + group = GTK_TOGGLE_BUTTON (scale_btn); + gtk_box_append (GTK_BOX (self->scale_bbox), scale_btn); + /* Set active before connecting the signal */ + if (is_selected) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scale_btn), TRUE); + + g_signal_connect_object (scale_btn, + "notify::active", + G_CALLBACK (on_scale_btn_active_changed_cb), + self, 0); + } + cc_display_settings_refresh_layout (self, self->folded); + + gtk_widget_set_visible (self->underscanning_row, + cc_display_monitor_supports_underscanning (self->selected_output) && + !cc_display_config_is_cloning (self->config)); + gtk_switch_set_active (GTK_SWITCH (self->underscanning_switch), + cc_display_monitor_get_underscanning (self->selected_output)); + + self->updating = TRUE; + g_object_thaw_notify ((GObject*) self->enabled_switch); + g_object_thaw_notify ((GObject*) self->orientation_row); + g_object_thaw_notify ((GObject*) self->refresh_rate_row); + g_object_thaw_notify ((GObject*) self->resolution_row); + g_object_thaw_notify ((GObject*) self->scale_combo_row); + g_object_thaw_notify ((GObject*) self->underscanning_switch); + self->updating = FALSE; + + return G_SOURCE_REMOVE; +} + +static void +on_output_changed_cb (CcDisplaySettings *self, + GParamSpec *pspec, + CcDisplayMonitor *output) +{ + /* Do this frmo an idle handler, because otherwise we may create an + * infinite loop triggering the notify::selected-index from the + * combo rows. */ + if (self->idle_udpate_id) + return; + + self->idle_udpate_id = g_idle_add ((GSourceFunc) cc_display_settings_rebuild_ui, self); +} + +static void +on_enabled_switch_active_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + if (self->updating) + return; + + cc_display_monitor_set_active (self->selected_output, + gtk_switch_get_active (self->enabled_switch)); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_orientation_selection_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + gint idx; + g_autoptr(GObject) obj = NULL; + + if (self->updating) + return; + + idx = adw_combo_row_get_selected (ADW_COMBO_ROW (self->orientation_row)); + obj = g_list_model_get_item (G_LIST_MODEL (self->orientation_list), idx); + + if (!obj) + return; + + cc_display_monitor_set_rotation (self->selected_output, + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (obj), "rotation-value"))); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_refresh_rate_selection_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + gint idx; + g_autoptr(CcDisplayMode) mode = NULL; + + if (self->updating) + return; + + idx = adw_combo_row_get_selected (ADW_COMBO_ROW (self->refresh_rate_row)); + mode = g_list_model_get_item (G_LIST_MODEL (self->refresh_rate_list), idx); + + if (!mode) + return; + + cc_display_monitor_set_mode (self->selected_output, mode); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_resolution_selection_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + gint idx; + g_autoptr(CcDisplayMode) mode = NULL; + + if (self->updating) + return; + + idx = adw_combo_row_get_selected (ADW_COMBO_ROW (self->resolution_row)); + mode = g_list_model_get_item (G_LIST_MODEL (self->resolution_list), idx); + + if (!mode) + return; + + /* This is the only row that can be changed when in cloning mode. */ + if (!cc_display_config_is_cloning (self->config)) + cc_display_monitor_set_mode (self->selected_output, mode); + else + cc_display_config_set_mode_on_all_outputs (self->config, mode); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_scale_btn_active_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + gdouble scale; + if (self->updating) + return; + + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + return; + + scale = *(gdouble*) g_object_get_data (G_OBJECT (widget), "scale"); + cc_display_monitor_set_scale (self->selected_output, + scale); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_scale_selection_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + int idx; + double scale; + g_autoptr(GObject) obj = NULL; + + if (self->updating) + return; + + idx = adw_combo_row_get_selected (ADW_COMBO_ROW (self->scale_combo_row)); + obj = g_list_model_get_item (G_LIST_MODEL (self->scale_list), idx); + if (!obj) + return; + scale = *(gdouble*) g_object_get_data (G_OBJECT (obj), "scale"); + + cc_display_monitor_set_scale (self->selected_output, scale); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_underscanning_switch_active_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + if (self->updating) + return; + + cc_display_monitor_set_underscanning (self->selected_output, + gtk_switch_get_active (GTK_SWITCH (self->underscanning_switch))); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +cc_display_settings_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object); + + switch (prop_id) + { + case PROP_HAS_ACCELEROMETER: + g_value_set_boolean (value, cc_display_settings_get_has_accelerometer (self)); + break; + + case PROP_CONFIG: + g_value_set_object (value, self->config); + break; + + case PROP_SELECTED_OUTPUT: + g_value_set_object (value, self->selected_output); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_settings_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object); + + switch (prop_id) + { + case PROP_HAS_ACCELEROMETER: + cc_display_settings_set_has_accelerometer (self, g_value_get_boolean (value)); + break; + + case PROP_CONFIG: + cc_display_settings_set_config (self, g_value_get_object (value)); + break; + + case PROP_SELECTED_OUTPUT: + cc_display_settings_set_selected_output (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_settings_finalize (GObject *object) +{ + CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object); + + g_clear_object (&self->config); + + g_clear_object (&self->orientation_list); + g_clear_object (&self->refresh_rate_list); + g_clear_object (&self->resolution_list); + g_clear_object (&self->scale_list); + + if (self->idle_udpate_id) + g_source_remove (self->idle_udpate_id); + self->idle_udpate_id = 0; + + G_OBJECT_CLASS (cc_display_settings_parent_class)->finalize (object); +} + +static void +cc_display_settings_class_init (CcDisplaySettingsClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->finalize = cc_display_settings_finalize; + gobject_class->get_property = cc_display_settings_get_property; + gobject_class->set_property = cc_display_settings_set_property; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-display-settings.ui"); + + props[PROP_HAS_ACCELEROMETER] = + g_param_spec_boolean ("has-accelerometer", "Has Accelerometer", + "If an accelerometre is available for the builtin display", + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + props[PROP_CONFIG] = + g_param_spec_object ("config", "Display Config", + "The display configuration to work with", + CC_TYPE_DISPLAY_CONFIG, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + props[PROP_SELECTED_OUTPUT] = + g_param_spec_object ("selected-output", "Selected Output", + "The output that is currently selected on the configuration", + CC_TYPE_DISPLAY_MONITOR, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + PROP_LAST, + props); + + g_signal_new ("updated", + CC_TYPE_DISPLAY_SETTINGS, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, CC_TYPE_DISPLAY_MONITOR); + + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, enabled_listbox); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, enabled_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, enabled_switch); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, orientation_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, refresh_rate_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, resolution_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_bbox); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_buttons_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_combo_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, underscanning_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, underscanning_switch); + + gtk_widget_class_bind_template_callback (widget_class, on_enabled_switch_active_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_orientation_selection_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_refresh_rate_selection_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_resolution_selection_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_scale_selection_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_underscanning_switch_active_changed_cb); +} + +static void +cc_display_settings_init (CcDisplaySettings *self) +{ + GtkExpression *expression; + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->orientation_list = G_LIST_MODEL (gtk_string_list_new (NULL)); + self->refresh_rate_list = g_list_store_new (CC_TYPE_DISPLAY_MODE); + self->resolution_list = g_list_store_new (CC_TYPE_DISPLAY_MODE); + self->scale_list = G_LIST_MODEL (gtk_string_list_new (NULL)); + + self->updating = TRUE; + + adw_combo_row_set_model (ADW_COMBO_ROW (self->orientation_row), + G_LIST_MODEL (self->orientation_list)); + adw_combo_row_set_model (ADW_COMBO_ROW (self->scale_combo_row), + G_LIST_MODEL (self->scale_list)); + + expression = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, 0, NULL, + G_CALLBACK (make_refresh_rate_string), + self, NULL); + adw_combo_row_set_expression (ADW_COMBO_ROW (self->refresh_rate_row), expression); + adw_combo_row_set_model (ADW_COMBO_ROW (self->refresh_rate_row), + G_LIST_MODEL (self->refresh_rate_list)); + + expression = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, 0, NULL, + G_CALLBACK (make_resolution_string), + self, NULL); + adw_combo_row_set_expression (ADW_COMBO_ROW (self->resolution_row), expression); + adw_combo_row_set_model (ADW_COMBO_ROW (self->resolution_row), + G_LIST_MODEL (self->resolution_list)); + + self->updating = FALSE; +} + +CcDisplaySettings* +cc_display_settings_new (void) +{ + return g_object_new (CC_TYPE_DISPLAY_SETTINGS, + NULL); +} + +gboolean +cc_display_settings_get_has_accelerometer (CcDisplaySettings *settings) +{ + return settings->has_accelerometer; +} + +void +cc_display_settings_set_has_accelerometer (CcDisplaySettings *self, + gboolean has_accelerometer) +{ + self->has_accelerometer = has_accelerometer; + + cc_display_settings_rebuild_ui (self); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); +} + +CcDisplayConfig* +cc_display_settings_get_config (CcDisplaySettings *self) +{ + return self->config; +} + +void +cc_display_settings_set_config (CcDisplaySettings *self, + CcDisplayConfig *config) +{ + const gchar *signals[] = { "rotation", "mode", "scale", "is-usable", "active" }; + GList *outputs, *l; + guint i; + + if (self->config) + { + outputs = cc_display_config_get_monitors (self->config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + g_signal_handlers_disconnect_by_data (output, self); + } + } + g_clear_object (&self->config); + + self->config = g_object_ref (config); + + /* Listen to all the signals */ + if (self->config) + { + outputs = cc_display_config_get_monitors (self->config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + for (i = 0; i < G_N_ELEMENTS (signals); ++i) + g_signal_connect_object (output, signals[i], G_CALLBACK (on_output_changed_cb), self, G_CONNECT_SWAPPED); + } + } + + cc_display_settings_set_selected_output (self, NULL); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); +} + +CcDisplayMonitor* +cc_display_settings_get_selected_output (CcDisplaySettings *self) +{ + return self->selected_output; +} + +void +cc_display_settings_set_selected_output (CcDisplaySettings *self, + CcDisplayMonitor *output) +{ + self->selected_output = output; + + cc_display_settings_rebuild_ui (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_OUTPUT]); +} + +void +cc_display_settings_refresh_layout (CcDisplaySettings *self, + gboolean folded) +{ + gboolean use_combo; + + self->folded = folded; + use_combo = self->num_scales > MAX_SCALE_BUTTONS || (self->num_scales > 2 && folded); + + gtk_widget_set_visible (self->scale_combo_row, use_combo); + gtk_widget_set_visible (self->scale_buttons_row, self->num_scales > 1 && !use_combo); +} + +void +cc_display_settings_set_multimonitor (CcDisplaySettings *self, + gboolean multimonitor) +{ + gtk_widget_set_visible (self->enabled_listbox, multimonitor); + + if (!multimonitor) + gtk_switch_set_active (GTK_SWITCH (self->enabled_switch), TRUE); +} diff --git a/panels/display/cc-display-settings.h b/panels/display/cc-display-settings.h new file mode 100644 index 0000000..4794414 --- /dev/null +++ b/panels/display/cc-display-settings.h @@ -0,0 +1,48 @@ +/* -*- mode: c; style: linux -*- + * + * Copyright (C) 2019 Red Hat, Inc. + * + * Written by: Benjamin Berg <bberg@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <adwaita.h> +#include "cc-display-config.h" + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_SETTINGS cc_display_settings_get_type () +G_DECLARE_FINAL_TYPE (CcDisplaySettings, cc_display_settings, CC, DISPLAY_SETTINGS, GtkBox); + +CcDisplaySettings* cc_display_settings_new (void); + +gboolean cc_display_settings_get_has_accelerometer (CcDisplaySettings *settings); +void cc_display_settings_set_has_accelerometer (CcDisplaySettings *settings, + gboolean has_accelerometer); +CcDisplayConfig* cc_display_settings_get_config (CcDisplaySettings *settings); +void cc_display_settings_set_config (CcDisplaySettings *settings, + CcDisplayConfig *config); +CcDisplayMonitor* cc_display_settings_get_selected_output (CcDisplaySettings *settings); +void cc_display_settings_set_selected_output (CcDisplaySettings *settings, + CcDisplayMonitor *output); +void cc_display_settings_refresh_layout (CcDisplaySettings *settings, + gboolean folded); +void cc_display_settings_set_multimonitor (CcDisplaySettings *self, + gboolean multimonitor); + +G_END_DECLS + diff --git a/panels/display/cc-display-settings.ui b/panels/display/cc-display-settings.ui new file mode 100644 index 0000000..64c44d1 --- /dev/null +++ b/panels/display/cc-display-settings.ui @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.0 --> +<interface> + <template class="CcDisplaySettings" parent="GtkBox"> + <property name="spacing">18</property> + <property name="orientation">vertical</property> + + <child> + <object class="GtkListBox" id="enabled_listbox"> + <property name="hexpand">True</property> + <property name="selection_mode">none</property> + <style> + <class name="boxed-list" /> + </style> + <child> + <object class="AdwActionRow" id="enabled_row"> + <property name="activatable-widget">enabled_switch</property> + <child> + <object class="GtkSwitch" id="enabled_switch"> + <property name="halign">end</property> + <property name="valign">center</property> + <accessibility> + <property name="label" translatable="yes">Enabled</property> + </accessibility> + <signal name="notify::active" handler="on_enabled_switch_active_changed_cb" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkListBox" id="listbox"> + <property name="hexpand">True</property> + <property name="selection_mode">none</property> + <style> + <class name="boxed-list" /> + </style> + <child> + <object class="AdwComboRow" id="orientation_row"> + <property name="width_request">100</property> + <property name="title" translatable="yes" context="display setting">Orientation</property> + <signal name="notify::selected-item" handler="on_orientation_selection_changed_cb" swapped="no"/> + </object> + </child> + <child> + <object class="AdwComboRow" id="resolution_row"> + <property name="width_request">100</property> + <property name="title" translatable="yes" context="display setting">Resolution</property> + <signal name="notify::selected-item" handler="on_resolution_selection_changed_cb" swapped="no"/> + </object> + </child> + <child> + <object class="AdwComboRow" id="refresh_rate_row"> + <property name="width_request">100</property> + <property name="title" translatable="yes">Refresh Rate</property> + <signal name="notify::selected-item" handler="on_refresh_rate_selection_changed_cb" swapped="no"/> + </object> + </child> + <child> + <object class="AdwActionRow" id="underscanning_row"> + <property name="width_request">100</property> + <property name="title" translatable="yes">Adjust for TV</property> + <property name="activatable-widget">underscanning_switch</property> + <child> + <object class="GtkSwitch" id="underscanning_switch"> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <signal name="notify::active" handler="on_underscanning_switch_active_changed_cb" swapped="no"/> + </object> + </child> + </object> + </child> + <child> + <object class="AdwActionRow" id="scale_buttons_row"> + <property name="width_request">100</property> + <property name="title" translatable="yes" context="display setting">Scale</property> + <child> + <object class="GtkBox" id="scale_bbox"> + <property name="halign">end</property> + <property name="valign">center</property> + <style> + <class name="linked" /> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="AdwComboRow" id="scale_combo_row"> + <property name="width_request">100</property> + <property name="title" translatable="yes" context="display setting">Scale</property> + <signal name="notify::selected-item" handler="on_scale_selection_changed_cb" swapped="no"/> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/display/cc-night-light-page.c b/panels/display/cc-night-light-page.c new file mode 100644 index 0000000..4b7b112 --- /dev/null +++ b/panels/display/cc-night-light-page.c @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2017 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <gdesktop-enums.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <math.h> + +#include "cc-night-light-page.h" + +#include "shell/cc-object-storage.h" +#include "cc-display-config-manager-dbus.h" + +struct _CcNightLightPage { + AdwBin parent; + + GtkWidget *night_light_settings; + GtkWidget *box_manual; + GtkButton *button_from_am; + GtkButton *button_from_pm; + GtkButton *button_to_am; + GtkButton *button_to_pm; + GtkWidget *infobar_unsupported; + GtkWidget *infobar_disabled; + GtkListBox *listbox; + GtkWidget *scale_color_temperature; + GtkWidget *night_light_toggle_switch; + GtkComboBox *schedule_type_combo; + GtkWidget *from_spinbuttons_box; + GtkWidget *spinbutton_from_hours; + GtkWidget *spinbutton_from_minutes; + GtkWidget *to_spinbuttons_box; + GtkWidget *spinbutton_to_hours; + GtkWidget *spinbutton_to_minutes; + GtkStack *stack_from; + GtkStack *stack_to; + + GtkAdjustment *adjustment_from_hours; + GtkAdjustment *adjustment_from_minutes; + GtkAdjustment *adjustment_to_hours; + GtkAdjustment *adjustment_to_minutes; + GtkAdjustment *adjustment_color_temperature; + + GSettings *settings_display; + GSettings *settings_clock; + GDBusProxy *proxy_color; + GDBusProxy *proxy_color_props; + GCancellable *cancellable; + gboolean ignore_value_changed; + guint timer_id; + GDesktopClockFormat clock_format; + + CcDisplayConfigManager *config_manager; +}; + +G_DEFINE_TYPE (CcNightLightPage, cc_night_light_page, ADW_TYPE_BIN); + +#define CLOCK_SCHEMA "org.gnome.desktop.interface" +#define DISPLAY_SCHEMA "org.gnome.settings-daemon.plugins.color" +#define CLOCK_FORMAT_KEY "clock-format" +#define NIGHT_LIGHT_PREVIEW_TIMEOUT_SECONDS 5 + +static void +dialog_adjustments_set_frac_hours (CcNightLightPage *self, + gdouble value, + GtkAdjustment *adj_hours, + GtkAdjustment *adj_mins, + GtkStack *stack, + GtkButton *button_am, + GtkButton *button_pm) +{ + gdouble hours; + gdouble mins = 0.f; + gboolean is_pm = FALSE; + gboolean is_24h; + + /* display the right thing for AM/PM */ + is_24h = self->clock_format == G_DESKTOP_CLOCK_FORMAT_24H; + mins = modf (value, &hours) * 60.f; + if (!is_24h) + { + if (hours > 12) + { + hours -= 12; + is_pm = TRUE; + } + else if (hours < 1.0) + { + hours += 12; + is_pm = FALSE; + } + else if (hours == 12.f) + { + is_pm = TRUE; + } + } + + g_debug ("setting adjustment %.3f to %.0f:%02.0f", value, hours, mins); + + self->ignore_value_changed = TRUE; + gtk_adjustment_set_value (GTK_ADJUSTMENT (adj_hours), hours); + gtk_adjustment_set_value (GTK_ADJUSTMENT (adj_mins), mins); + self->ignore_value_changed = FALSE; + + gtk_widget_set_visible (GTK_WIDGET (stack), !is_24h); + gtk_stack_set_visible_child (stack, is_pm ? GTK_WIDGET (button_pm) : GTK_WIDGET (button_am)); +} + +static void +dialog_update_state (CcNightLightPage *self) +{ + if (cc_display_config_manager_get_night_light_supported (self->config_manager)) + { + gboolean automatic; + gboolean disabled_until_tomorrow = FALSE; + gboolean enabled; + gdouble value = 0.f; + + /* only show the infobar if we are disabled */ + if (self->proxy_color != NULL) + { + g_autoptr(GVariant) disabled = NULL; + disabled = g_dbus_proxy_get_cached_property (self->proxy_color, + "DisabledUntilTomorrow"); + if (disabled != NULL) + disabled_until_tomorrow = g_variant_get_boolean (disabled); + } + gtk_widget_set_visible (self->infobar_disabled, disabled_until_tomorrow); + + /* make things insensitive if the switch is disabled */ + enabled = g_settings_get_boolean (self->settings_display, "night-light-enabled"); + automatic = g_settings_get_boolean (self->settings_display, "night-light-schedule-automatic"); + + gtk_widget_set_sensitive (self->box_manual, enabled && !automatic); + + gtk_combo_box_set_active_id (self->schedule_type_combo, automatic ? "automatic" : "manual"); + + /* set from */ + if (automatic && self->proxy_color != NULL) + { + g_autoptr(GVariant) sunset = NULL; + sunset = g_dbus_proxy_get_cached_property (self->proxy_color, "Sunset"); + if (sunset != NULL) + { + value = g_variant_get_double (sunset); + } + else + { + value = 16.0f; + g_warning ("no sunset data, using %02.2f", value); + } + } + else + { + value = g_settings_get_double (self->settings_display, "night-light-schedule-from"); + value = fmod (value, 24.f); + } + dialog_adjustments_set_frac_hours (self, value, + self->adjustment_from_hours, + self->adjustment_from_minutes, + self->stack_from, + self->button_from_am, + self->button_from_pm); + + /* set to */ + if (automatic && self->proxy_color != NULL) + { + g_autoptr(GVariant) sunset = NULL; + sunset = g_dbus_proxy_get_cached_property (self->proxy_color, "Sunrise"); + if (sunset != NULL) + { + value = g_variant_get_double (sunset); + } + else + { + value = 8.0f; + g_warning ("no sunrise data, using %02.2f", value); + } + } + else + { + value = g_settings_get_double (self->settings_display, "night-light-schedule-to"); + value = fmod (value, 24.f); + } + dialog_adjustments_set_frac_hours (self, value, + self->adjustment_to_hours, + self->adjustment_to_minutes, + self->stack_to, + self->button_to_am, + self->button_to_pm); + + self->ignore_value_changed = TRUE; + value = (gdouble) g_settings_get_uint (self->settings_display, "night-light-temperature"); + gtk_adjustment_set_value (self->adjustment_color_temperature, value); + self->ignore_value_changed = FALSE; + } + else + { + gtk_widget_set_visible (self->infobar_unsupported, TRUE); + gtk_widget_set_visible (self->infobar_disabled, FALSE); + gtk_widget_set_sensitive (self->night_light_settings, FALSE); + } +} + +static void +build_schedule_combo_row (CcNightLightPage *self) +{ + gboolean automatic; + gboolean enabled; + + self->ignore_value_changed = TRUE; + + + enabled = g_settings_get_boolean (self->settings_display, "night-light-enabled"); + automatic = g_settings_get_boolean (self->settings_display, "night-light-schedule-automatic"); + + gtk_widget_set_sensitive (self->box_manual, enabled && !automatic); + + gtk_combo_box_set_active_id (self->schedule_type_combo, automatic ? "automatic" : "manual"); + + self->ignore_value_changed = FALSE; +} + +static void +on_schedule_type_combo_active_changed_cb (GtkComboBox *combo_box, + GParamSpec *pspec, + CcNightLightPage *self) +{ + const gchar *active_id; + gboolean automatic; + + if (self->ignore_value_changed) + return; + + active_id = gtk_combo_box_get_active_id (combo_box); + automatic = g_str_equal (active_id, "automatic"); + + g_settings_set_boolean (self->settings_display, "night-light-schedule-automatic", automatic); +} + +static gboolean +dialog_tick_cb (gpointer user_data) +{ + CcNightLightPage *self = (CcNightLightPage *) user_data; + dialog_update_state (self); + return G_SOURCE_CONTINUE; +} + +static void +dialog_enabled_notify_cb (GtkSwitch *sw, + GParamSpec *pspec, + CcNightLightPage *self) +{ + g_settings_set_boolean (self->settings_display, "night-light-enabled", + gtk_switch_get_active (sw)); +} + +static void +dialog_undisable_call_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GVariant) val = NULL; + g_autoptr(GError) error = NULL; + + val = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), + res, &error); + if (val == NULL) + { + g_warning ("failed to undisable: %s", error->message); + return; + } +} + +static void +dialog_undisable_clicked_cb (GtkButton *button, + CcNightLightPage *self) +{ + g_dbus_proxy_call (self->proxy_color_props, + "Set", + g_variant_new ("(ssv)", + "org.gnome.SettingsDaemon.Color", + "DisabledUntilTomorrow", + g_variant_new_boolean (FALSE)), + G_DBUS_CALL_FLAGS_NONE, + 5000, + self->cancellable, + dialog_undisable_call_cb, + self); +} + +static gdouble +dialog_adjustments_get_frac_hours (CcNightLightPage *self, + GtkAdjustment *adj_hours, + GtkAdjustment *adj_mins, + GtkStack *stack) +{ + gdouble value; + + value = gtk_adjustment_get_value (adj_hours); + value += gtk_adjustment_get_value (adj_mins) / 60.0f; + + if (g_strcmp0 (gtk_stack_get_visible_child_name (stack), "pm") == 0) + value += 12.f; + + return value; +} + +static void +dialog_time_from_value_changed_cb (GtkAdjustment *adjustment, + CcNightLightPage *self) +{ + gdouble value; + + if (self->ignore_value_changed) + return; + + value = dialog_adjustments_get_frac_hours (self, + self->adjustment_from_hours, + self->adjustment_from_minutes, + self->stack_from); + + if (value >= 24.f) + value = fmod (value, 24); + + g_debug ("new value = %.3f", value); + + g_settings_set_double (self->settings_display, "night-light-schedule-from", value); +} + +static void +dialog_time_to_value_changed_cb (GtkAdjustment *adjustment, + CcNightLightPage *self) +{ + gdouble value; + + if (self->ignore_value_changed) + return; + + value = dialog_adjustments_get_frac_hours (self, + self->adjustment_to_hours, + self->adjustment_to_minutes, + self->stack_to); + if (value >= 24.f) + value = fmod (value, 24); + + g_debug ("new value = %.3f", value); + + g_settings_set_double (self->settings_display, "night-light-schedule-to", value); +} + +static void +dialog_color_temperature_value_changed_cb (GtkAdjustment *adjustment, + CcNightLightPage *self) +{ + gdouble value; + + if (self->ignore_value_changed) + return; + + value = gtk_adjustment_get_value (adjustment); + + g_debug ("new value = %.0f", value); + + if (self->proxy_color != NULL) + g_dbus_proxy_call (self->proxy_color, + "NightLightPreview", + g_variant_new ("(u)", NIGHT_LIGHT_PREVIEW_TIMEOUT_SECONDS), + G_DBUS_CALL_FLAGS_NONE, + 5000, + NULL, + NULL, + NULL); + + g_settings_set_uint (self->settings_display, "night-light-temperature", (guint) value); +} + +static void +dialog_color_properties_changed_cb (CcNightLightPage *self) +{ + dialog_update_state (self); +} + +static void +dialog_got_proxy_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CcNightLightPage *self = (CcNightLightPage *) user_data; + GDBusProxy *proxy; + g_autoptr(GError) error = NULL; + + proxy = cc_object_storage_create_dbus_proxy_finish (res, &error); + if (proxy == NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("failed to connect to g-s-d: %s", error->message); + return; + } + + self->proxy_color = proxy; + + g_signal_connect_object (self->proxy_color, "g-properties-changed", + G_CALLBACK (dialog_color_properties_changed_cb), self, G_CONNECT_SWAPPED); + dialog_update_state (self); + self->timer_id = g_timeout_add_seconds (10, dialog_tick_cb, self); +} + +static void +dialog_got_proxy_props_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CcNightLightPage *self = (CcNightLightPage *) user_data; + GDBusProxy *proxy; + g_autoptr(GError) error = NULL; + + proxy = cc_object_storage_create_dbus_proxy_finish (res, &error); + if (proxy == NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("failed to connect to g-s-d: %s", error->message); + return; + } + + self->proxy_color_props = proxy; +} + +static gboolean +dialog_format_minutes_combobox (GtkSpinButton *spin, + CcNightLightPage *self) +{ + GtkAdjustment *adjustment; + g_autofree gchar *text = NULL; + adjustment = gtk_spin_button_get_adjustment (spin); + text = g_strdup_printf ("%02.0f", gtk_adjustment_get_value (adjustment)); + gtk_editable_set_text (GTK_EDITABLE (spin), text); + return TRUE; +} + +static gboolean +dialog_format_hours_combobox (GtkSpinButton *spin, + CcNightLightPage *self) +{ + GtkAdjustment *adjustment; + g_autofree gchar *text = NULL; + adjustment = gtk_spin_button_get_adjustment (spin); + if (self->clock_format == G_DESKTOP_CLOCK_FORMAT_12H) + text = g_strdup_printf ("%.0f", gtk_adjustment_get_value (adjustment)); + else + text = g_strdup_printf ("%02.0f", gtk_adjustment_get_value (adjustment)); + gtk_editable_set_text (GTK_EDITABLE (spin), text); + return TRUE; +} + +static void +dialog_update_adjustments (CcNightLightPage *self) +{ + /* from */ + if (self->clock_format == G_DESKTOP_CLOCK_FORMAT_24H) + { + gtk_adjustment_set_lower (self->adjustment_from_hours, 0); + gtk_adjustment_set_upper (self->adjustment_from_hours, 23); + } + else + { + if (gtk_adjustment_get_value (self->adjustment_from_hours) > 12) + gtk_stack_set_visible_child (self->stack_from, GTK_WIDGET (self->button_from_pm)); + + gtk_adjustment_set_lower (self->adjustment_from_hours, 1); + gtk_adjustment_set_upper (self->adjustment_from_hours, 12); + } + + /* to */ + if (self->clock_format == G_DESKTOP_CLOCK_FORMAT_24H) + { + gtk_adjustment_set_lower (self->adjustment_to_hours, 0); + gtk_adjustment_set_upper (self->adjustment_to_hours, 23); + } + else + { + if (gtk_adjustment_get_value (self->adjustment_to_hours) > 12) + gtk_stack_set_visible_child (self->stack_to, GTK_WIDGET (self->button_to_pm)); + + gtk_adjustment_set_lower (self->adjustment_to_hours, 1); + gtk_adjustment_set_upper (self->adjustment_to_hours, 12); + } +} + +static void +dialog_settings_changed_cb (CcNightLightPage *self) +{ + dialog_update_state (self); +} + +static void +dialog_clock_settings_changed_cb (CcNightLightPage *self) +{ + self->clock_format = g_settings_get_enum (self->settings_clock, CLOCK_FORMAT_KEY); + + /* uncontionally widen this to avoid truncation */ + gtk_adjustment_set_lower (self->adjustment_from_hours, 0); + gtk_adjustment_set_upper (self->adjustment_from_hours, 23); + gtk_adjustment_set_lower (self->adjustment_to_hours, 0); + gtk_adjustment_set_upper (self->adjustment_to_hours, 23); + + /* update spinbuttons */ + gtk_spin_button_update (GTK_SPIN_BUTTON (self->spinbutton_from_hours)); + gtk_spin_button_update (GTK_SPIN_BUTTON (self->spinbutton_to_hours)); + + /* update UI */ + dialog_update_state (self); + dialog_update_adjustments (self); +} + +static void +dialog_am_pm_from_button_clicked_cb (GtkButton *button, + CcNightLightPage *self) +{ + gdouble value; + value = g_settings_get_double (self->settings_display, "night-light-schedule-from"); + if (value > 12.f) + value -= 12.f; + else + value += 12.f; + if (value >= 24.f) + value = fmod (value, 24); + g_settings_set_double (self->settings_display, "night-light-schedule-from", value); + g_debug ("new value = %.3f", value); +} + +static void +dialog_am_pm_to_button_clicked_cb (GtkButton *button, + CcNightLightPage *self) +{ + gdouble value; + value = g_settings_get_double (self->settings_display, "night-light-schedule-to"); + if (value > 12.f) + value -= 12.f; + else + value += 12.f; + if (value >= 24.f) + value = fmod (value, 24); + g_settings_set_double (self->settings_display, "night-light-schedule-to", value); + g_debug ("new value = %.3f", value); +} + +static void +config_manager_changed_cb (CcDisplayConfigManager *config_manager, + CcNightLightPage *self) +{ + dialog_update_state (self); +} + +/* GObject overrides */ +static void +cc_night_light_page_finalize (GObject *object) +{ + CcNightLightPage *self = CC_NIGHT_LIGHT_PAGE (object); + + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->cancellable); + g_clear_object (&self->proxy_color); + g_clear_object (&self->proxy_color_props); + g_clear_object (&self->settings_display); + g_clear_object (&self->settings_clock); + if (self->timer_id > 0) + g_source_remove (self->timer_id); + + G_OBJECT_CLASS (cc_night_light_page_parent_class)->finalize (object); +} + +static void +cc_night_light_page_class_init (CcNightLightPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_night_light_page_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-night-light-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, adjustment_from_hours); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, adjustment_from_minutes); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, adjustment_to_hours); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, adjustment_to_minutes); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, adjustment_color_temperature); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, night_light_settings); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, box_manual); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, button_from_am); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, button_from_pm); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, button_to_am); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, button_to_pm); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, infobar_unsupported); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, infobar_disabled); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, listbox); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, night_light_toggle_switch); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, schedule_type_combo); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, scale_color_temperature); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, from_spinbuttons_box); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, spinbutton_from_hours); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, spinbutton_from_minutes); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, to_spinbuttons_box); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, spinbutton_to_hours); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, spinbutton_to_minutes); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, stack_from); + gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, stack_to); + + gtk_widget_class_bind_template_callback (widget_class, dialog_am_pm_from_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, dialog_am_pm_to_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, dialog_enabled_notify_cb); + gtk_widget_class_bind_template_callback (widget_class, dialog_format_hours_combobox); + gtk_widget_class_bind_template_callback (widget_class, dialog_format_minutes_combobox); + gtk_widget_class_bind_template_callback (widget_class, dialog_time_from_value_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, dialog_time_to_value_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, dialog_color_temperature_value_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, dialog_undisable_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_schedule_type_combo_active_changed_cb); + +} + +static void +cc_night_light_page_init (CcNightLightPage *self) +{ + g_autoptr(GtkCssProvider) provider = NULL; + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_scale_add_mark (GTK_SCALE (self->scale_color_temperature), + 1700, GTK_POS_BOTTOM, + NULL); + + gtk_scale_add_mark (GTK_SCALE (self->scale_color_temperature), + 2700, GTK_POS_BOTTOM, + NULL); + + gtk_scale_add_mark (GTK_SCALE (self->scale_color_temperature), + 3700, GTK_POS_BOTTOM, + NULL); + + gtk_scale_add_mark (GTK_SCALE (self->scale_color_temperature), + 4700, GTK_POS_BOTTOM, + NULL); + + self->cancellable = g_cancellable_new (); + self->settings_display = g_settings_new (DISPLAY_SCHEMA); + + g_signal_connect_object (self->settings_display, "changed", G_CALLBACK (dialog_settings_changed_cb), self, G_CONNECT_SWAPPED); + + build_schedule_combo_row (self); + + g_settings_bind (self->settings_display, "night-light-enabled", + self->night_light_toggle_switch, "active", + G_SETTINGS_BIND_DEFAULT); + + g_settings_bind_writable (self->settings_display, "night-light-enabled", + self->night_light_toggle_switch, "sensitive", + FALSE); + + g_settings_bind_writable (self->settings_display, "night-light-schedule-from", + self->spinbutton_from_hours, "sensitive", + FALSE); + g_settings_bind_writable (self->settings_display, "night-light-schedule-from", + self->spinbutton_from_minutes, "sensitive", + FALSE); + g_settings_bind_writable (self->settings_display, "night-light-schedule-to", + self->spinbutton_to_minutes, "sensitive", + FALSE); + g_settings_bind_writable (self->settings_display, "night-light-schedule-to", + self->spinbutton_to_minutes, "sensitive", + FALSE); + + /* use custom CSS */ + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/display/night-light.css"); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.SettingsDaemon.Color", + "/org/gnome/SettingsDaemon/Color", + "org.gnome.SettingsDaemon.Color", + self->cancellable, + dialog_got_proxy_cb, + self); + + cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.SettingsDaemon.Color", + "/org/gnome/SettingsDaemon/Color", + "org.freedesktop.DBus.Properties", + self->cancellable, + dialog_got_proxy_props_cb, + self); + + /* clock settings_display */ + self->settings_clock = g_settings_new (CLOCK_SCHEMA); + self->clock_format = g_settings_get_enum (self->settings_clock, CLOCK_FORMAT_KEY); + dialog_update_adjustments (self); + g_signal_connect_object (self->settings_clock, + "changed::" CLOCK_FORMAT_KEY, + G_CALLBACK (dialog_clock_settings_changed_cb), + self, G_CONNECT_SWAPPED); + + if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) + { + gtk_widget_set_direction (self->to_spinbuttons_box, GTK_TEXT_DIR_LTR); + gtk_widget_set_direction (self->from_spinbuttons_box, GTK_TEXT_DIR_LTR); + } + + self->config_manager = cc_display_config_manager_dbus_new (); + g_signal_connect (self->config_manager, "changed", + G_CALLBACK (config_manager_changed_cb), self); + + dialog_update_state (self); +} + +CcNightLightPage * +cc_night_light_page_new (void) +{ + return g_object_new (CC_TYPE_NIGHT_LIGHT_PAGE, + NULL); +} + diff --git a/panels/display/cc-night-light-page.h b/panels/display/cc-night-light-page.h new file mode 100644 index 0000000..7e7be4b --- /dev/null +++ b/panels/display/cc-night-light-page.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include <adwaita.h> + +G_BEGIN_DECLS + +#define CC_TYPE_NIGHT_LIGHT_PAGE (cc_night_light_page_get_type ()) +G_DECLARE_FINAL_TYPE (CcNightLightPage, cc_night_light_page, CC, NIGHT_LIGHT_PAGE, AdwBin) + +CcNightLightPage* cc_night_light_page_new (void); + +G_END_DECLS diff --git a/panels/display/cc-night-light-page.ui b/panels/display/cc-night-light-page.ui new file mode 100644 index 0000000..6e5b12b --- /dev/null +++ b/panels/display/cc-night-light-page.ui @@ -0,0 +1,438 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcNightLightPage" parent="AdwBin"> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <child> + <object class="GtkInfoBar" id="infobar_unsupported"> + <property name="visible">False</property> + <property name="name">infobar_unsupported</property> + <property name="message-type">warning</property> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <property name="hexpand">True</property> + <property name="spacing">16</property> + <child> + <object class="GtkLabel"> + <property name="halign">start</property> + <property name="margin-start">6</property> + <property name="hexpand">False</property> + <property name="label" translatable="yes">Night Light unavailable</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="margin-start">6</property> + <property name="label" translatable="yes">This could be the result of the graphics driver being used, or the desktop being used remotely</property> + <property name="wrap">True</property> + <property name="xalign">0.0</property> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="night_light_settings"> + <property name="halign">center</property> + <property name="valign">start</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkInfoBar" id="infobar_disabled"> + <property name="name">infobar_disabled</property> + <child> + <object class="GtkBox"> + <property name="hexpand">True</property> + <property name="spacing">16</property> + <child> + <object class="GtkLabel"> + <property name="halign">start</property> + <property name="margin-start">12</property> + <property name="hexpand">False</property> + <property name="wrap">True</property> + <property name="label" translatable="yes" comments="Inhibit the redshift functionality until the next day starts">Temporarily Disabled Until Tomorrow</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + </object> + </child> + <child> + <object class="GtkButton" id="button_undisable"> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="label" translatable="yes" comments="This cancels the redshift inhibit.">Restart Filter</property> + <property name="name">button_undisable</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="valign">GTK_ALIGN_CENTER</property> + <signal name="clicked" handler="dialog_undisable_clicked_cb" object="CcNightLightPage" swapped="no" /> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkBox"> + <property name="margin_top">30</property> + <property name="margin_end">12</property> + <property name="margin_start">12</property> + <property name="margin_bottom">36</property> + <property name="orientation">vertical</property> + <property name="spacing">26</property> + <child> + <object class="GtkLabel"> + <property name="halign">start</property> + <property name="valign">start</property> + <property name="label" translatable="yes">Night light makes the screen color warmer. This can help to prevent eye strain and sleeplessness.</property> + <property name="wrap">True</property> + <property name="max_width_chars">60</property> + <property name="xalign">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkListBox" id="listbox"> + <property name="selection-mode">none</property> + + <style> + <class name="boxed-list" /> + </style> + + <!-- Night Light --> + <child> + <object class="AdwActionRow"> + <property name="title" translatable="yes">Night Light</property> + <property name="activatable-widget">night_light_toggle_switch</property> + + <child> + <object class="GtkSwitch" id="night_light_toggle_switch"> + <property name="valign">center</property> + <accessibility> + <property name="label" translatable="yes">Enable</property> + </accessibility> + </object> + </child> + </object> + </child> + + <!-- Schedule --> + <child> + <object class="AdwActionRow" id="schedule_row"> + <property name="title" translatable="yes">Schedule</property> + <property name="sensitive" bind-source="night_light_toggle_switch" bind-property="active" bind-flags="default|sync-create" /> + + <child> + <object class="GtkComboBoxText" id="schedule_type_combo"> + <property name="valign">center</property> + <signal name="notify::active" handler="on_schedule_type_combo_active_changed_cb" object="CcNightLightPage" swapped="no" /> + <items> + <item translatable="yes" id="automatic">Sunset to Sunrise</item> + <item translatable="yes" id="manual">Manual Schedule</item> + </items> + <accessibility> + <relation name="labelled-by">schedule_row</relation> + </accessibility> + </object> + </child> + </object> + </child> + + <!-- Time --> + <child> + <object class="AdwActionRow"> + <property name="title" translatable="yes">Times</property> + <property name="sensitive" bind-source="night_light_toggle_switch" bind-property="active" bind-flags="default|sync-create" /> + + <child> + <object class="GtkBox" id="box_manual"> + <property name="spacing">6</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <style> + <class name="time-widget" /> + </style> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">From</property> + <property name="mnemonic_widget">spinbutton_from_hours</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkBox" id="from_spinbuttons_box"> + <property name="spacing">4</property> + <child> + <object class="GtkSpinButton" id="spinbutton_from_hours"> + <property name="can_focus">True</property> + <property name="max_width_chars">2</property> + <property name="text">4</property> + <property name="orientation">vertical</property> + <property name="adjustment">adjustment_from_hours</property> + <property name="numeric">True</property> + <property name="wrap">True</property> + <property name="value">4</property> + <signal name="output" handler="dialog_format_hours_combobox" object="CcNightLightPage" swapped="no" /> + <style> + <class name="padded-spinbutton"/> + </style> + <accessibility> + <property name="description" translatable="yes">Hour</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">:</property> + </object> + </child> + <child> + <object class="GtkSpinButton" id="spinbutton_from_minutes"> + <property name="can_focus">True</property> + <property name="max_width_chars">2</property> + <property name="text">0</property> + <property name="orientation">vertical</property> + <property name="adjustment">adjustment_from_minutes</property> + <property name="numeric">True</property> + <property name="wrap">True</property> + <signal name="output" handler="dialog_format_minutes_combobox" object="CcNightLightPage" swapped="no" /> + <style> + <class name="padded-spinbutton"/> + </style> + <accessibility> + <property name="description" translatable="yes">Minute</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkStack" id="stack_from"> + <property name="hhomogeneous">False</property> + <property name="vhomogeneous">False</property> + <child> + <object class="GtkButton" id="button_from_am"> + <property name="label" translatable="yes" comments="This is the short form for the time period in the morning">AM</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="valign">center</property> + <signal name="clicked" handler="dialog_am_pm_from_button_clicked_cb" object="CcNightLightPage" swapped="no" /> + <style> + <class name="unpadded-button"/> + </style> + </object> + </child> + <child> + <object class="GtkButton" id="button_from_pm"> + <property name="label" translatable="yes" comments="This is the short form for the time period in the afternoon">PM</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="valign">center</property> + <signal name="clicked" handler="dialog_am_pm_from_button_clicked_cb" object="CcNightLightPage" swapped="no" /> + <style> + <class name="unpadded-button"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="margin-start">6</property> + <property name="label" translatable="yes">To</property> + <property name="mnemonic_widget">spinbutton_to_hours</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkBox" id="to_spinbuttons_box"> + <property name="spacing">4</property> + <child> + <object class="GtkSpinButton" id="spinbutton_to_hours"> + <property name="can_focus">True</property> + <property name="max_width_chars">2</property> + <property name="text">4</property> + <property name="orientation">vertical</property> + <property name="adjustment">adjustment_to_hours</property> + <property name="numeric">True</property> + <property name="wrap">True</property> + <property name="value">4</property> + <signal name="output" handler="dialog_format_hours_combobox" object="CcNightLightPage" swapped="no" /> + <style> + <class name="padded-spinbutton"/> + </style> + <accessibility> + <property name="description" translatable="yes">Hour</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">:</property> + </object> + </child> + <child> + <object class="GtkSpinButton" id="spinbutton_to_minutes"> + <property name="can_focus">True</property> + <property name="max_width_chars">2</property> + <property name="text">0</property> + <property name="orientation">vertical</property> + <property name="adjustment">adjustment_to_minutes</property> + <property name="numeric">True</property> + <property name="wrap">True</property> + <signal name="output" handler="dialog_format_minutes_combobox" object="CcNightLightPage" swapped="no" /> + <style> + <class name="padded-spinbutton"/> + </style> + <accessibility> + <property name="description" translatable="yes">Minute</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkStack" id="stack_to"> + <property name="hhomogeneous">False</property> + <property name="vhomogeneous">False</property> + <child> + <object class="GtkButton" id="button_to_am"> + <property name="label" translatable="yes">AM</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="valign">center</property> + <signal name="clicked" handler="dialog_am_pm_to_button_clicked_cb" object="CcNightLightPage" swapped="no" /> + <style> + <class name="unpadded-button"/> + </style> + </object> + </child> + <child> + <object class="GtkButton" id="button_to_pm"> + <property name="label" translatable="yes">PM</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="valign">center</property> + <signal name="clicked" handler="dialog_am_pm_to_button_clicked_cb" object="CcNightLightPage" swapped="no" /> + <style> + <class name="unpadded-button"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + + <!-- Color Temperature --> + <child> + <object class="AdwPreferencesRow"> + <property name="activatable">False</property> + <property name="sensitive" bind-source="night_light_toggle_switch" bind-property="active" bind-flags="default|sync-create" /> + + <child> + <object class="GtkBox"> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="spacing">6</property> + <property name="orientation">vertical</property> + + <child> + <object class="GtkLabel" id="title"> + <property name="label" translatable="yes">Color Temperature</property> + <property name="ellipsize">none</property> + <property name="lines">0</property> + <property name="xalign">0.0</property> + <property name="mnemonic_widget">scale_color_temperature</property> + <style> + <class name="title"/> + </style> + </object> + </child> + + <child> + <object class="GtkScale" id="scale_color_temperature"> + <property name="hexpand">True</property> + <property name="width-request">280</property> + <property name="adjustment">adjustment_color_temperature</property> + <property name="inverted">True</property> + <property name="restrict_to_fill_level">False</property> + <property name="fill_level">1</property> + <property name="digits">0</property> + <property name="draw_value">False</property> + <property name="has_origin">False</property> + <property name="value_pos">bottom</property> + <style> + <class name="night-light-temperature"/> + </style> + </object> + </child> + + </object> + </child> + + </object> + </child> + + </object> + </child> + + </object> + </child> + + </object> + </child> + </object> + </child> + </template> + + <object class="GtkAdjustment" id="adjustment_from_hours"> + <property name="upper">23</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + <signal name="value-changed" handler="dialog_time_from_value_changed_cb" object="CcNightLightPage" swapped="no" /> + </object> + <object class="GtkAdjustment" id="adjustment_from_minutes"> + <property name="upper">59</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + <signal name="value-changed" handler="dialog_time_from_value_changed_cb" object="CcNightLightPage" swapped="no" /> + </object> + <object class="GtkAdjustment" id="adjustment_to_hours"> + <property name="upper">23</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + <signal name="value-changed" handler="dialog_time_to_value_changed_cb" object="CcNightLightPage" swapped="no" /> + </object> + <object class="GtkAdjustment" id="adjustment_to_minutes"> + <property name="upper">59</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + <signal name="value-changed" handler="dialog_time_to_value_changed_cb" object="CcNightLightPage" swapped="no" /> + </object> + <object class="GtkAdjustment" id="adjustment_color_temperature"> + <property name="lower">1700</property> + <property name="upper">4700</property> + <property name="step_increment">100</property> + <property name="page_increment">500</property> + <signal name="value-changed" handler="dialog_color_temperature_value_changed_cb" object="CcNightLightPage" swapped="no" /> + </object> +</interface> diff --git a/panels/display/display-arrangement.css b/panels/display/display-arrangement.css new file mode 100644 index 0000000..a3bc80b --- /dev/null +++ b/panels/display/display-arrangement.css @@ -0,0 +1,33 @@ +display-arrangement { + font-weight: bold; + font-size: larger; +} + +display-arrangement.monitor { + border: solid 1px @borders; + margin: 0px 0px 1px 1px; + background: @theme_bg_color; + padding: 0.4em; + border-radius: 5px; +} + +display-arrangement.monitor.primary { + border-top: 0.4em solid #000000; +} + +display-arrangement.monitor-label { + border-radius: 50%; + min-width: 1.5em; + min-height: 1.5em; + color: #000; + background: #ddd; +} + +label.monitor-label { + border-radius: 50%; + font-weight: bold; + min-width: 1.5em; + min-height: 1.5em; + color: #000; + background: #ddd; +} diff --git a/panels/display/display.gresource.xml b/panels/display/display.gresource.xml new file mode 100644 index 0000000..fcac1a2 --- /dev/null +++ b/panels/display/display.gresource.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/display"> + <file preprocess="xml-stripblanks">cc-display-panel.ui</file> + <file preprocess="xml-stripblanks">cc-display-settings.ui</file> + <file preprocess="xml-stripblanks">cc-night-light-page.ui</file> + <file>display-arrangement.css</file> + <file>night-light.css</file> + </gresource> +</gresources> diff --git a/panels/display/gnome-display-panel.desktop.in.in b/panels/display/gnome-display-panel.desktop.in.in new file mode 100644 index 0000000..61f5ee7 --- /dev/null +++ b/panels/display/gnome-display-panel.desktop.in.in @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Displays +Comment=Choose how to use connected monitors and projectors +Exec=gnome-control-center display +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=org.gnome.Settings-display-symbolic +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-DevicesSettings; +OnlyShowIn=GNOME;Unity; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=Screen resolution +X-GNOME-Bugzilla-Version=@VERSION@ +# Translators: Search terms to find the Displays panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Panel;Projector;xrandr;Screen;Resolution;Refresh;Monitor;Night;Light;Blue;redshift;color;sunset;sunrise; diff --git a/panels/display/icons/meson.build b/panels/display/icons/meson.build new file mode 100644 index 0000000..91a313d --- /dev/null +++ b/panels/display/icons/meson.build @@ -0,0 +1,4 @@ +install_data( + 'scalable/org.gnome.Settings-display-symbolic.svg', + install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps') +) diff --git a/panels/display/icons/scalable/org.gnome.Settings-display-symbolic.svg b/panels/display/icons/scalable/org.gnome.Settings-display-symbolic.svg new file mode 100644 index 0000000..04f7a3b --- /dev/null +++ b/panels/display/icons/scalable/org.gnome.Settings-display-symbolic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <path d="m 12 1 c 1.660156 0 3 1.339844 3 3 v 6 c 0 1.660156 -1.339844 3 -3 3 h -8 c -1.660156 0 -3 -1.339844 -3 -3 v -6 c 0 -1.660156 1.339844 -3 3 -3 z m 0 2 h -8 c -0.554688 0 -1 0.445312 -1 1 v 6 c 0 0.554688 0.445312 1 1 1 h 8 c 0.554688 0 1 -0.445312 1 -1 v -6 c 0 -0.554688 -0.445312 -1 -1 -1 z m -4 11 c -5 0 -5 1 -5 1 c 0 1 1 1 1 1 h 8 c 1 0 1 -1 1 -1 s 0 -1 -5 -1 z m 0 0" fill="#2e3436"/> +</svg> diff --git a/panels/display/meson.build b/panels/display/meson.build new file mode 100644 index 0000000..726a898 --- /dev/null +++ b/panels/display/meson.build @@ -0,0 +1,64 @@ +panels_list += cappletname +desktop = 'gnome-@0@-panel.desktop'.format(cappletname) + +desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf +) + +i18n.merge_file( + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: control_center_desktopdir +) + +sources = files( + 'cc-display-panel.c', + 'cc-display-arrangement.c', + 'cc-display-config.c', + 'cc-display-config-dbus.c', + 'cc-display-config-manager-dbus.c', + 'cc-display-config-manager.c', + 'cc-display-settings.c', + 'cc-night-light-page.c', +) + +resource_data = files( + 'cc-display-panel.ui', + 'cc-display-settings.ui', + 'cc-night-light-page.ui', +) + +sources += gnome.compile_resources( + 'cc-' + cappletname + '-resources', + cappletname + '.gresource.xml', + source_dir: ['.', 'icons'], + c_name: 'cc_' + cappletname, + dependencies: resource_data, + export: true +) + +deps = common_deps + [ + colord_dep, + gnome_rr_dep, + m_dep, + upower_glib_dep +] + +cflags += [ + '-DDATADIR="@0@"'.format(control_center_datadir) +] + +panels_libs += static_library( + cappletname, + sources: sources, + include_directories: [ top_inc, common_inc ], + dependencies: deps, + c_args: cflags +) + +subdir('icons') diff --git a/panels/display/night-light.css b/panels/display/night-light.css new file mode 100644 index 0000000..02c0a09 --- /dev/null +++ b/panels/display/night-light.css @@ -0,0 +1,28 @@ +/* color selection by Daniel Foré and elementary OS */ +@define-color ORANGE_100 #ffc27d; +@define-color ORANGE_500 #f37329; +@define-color base_color white; +@define-color bg_color shade(@base_color, 0.96); + +/* Hide the marks at the beginning and the end */ +.night-light-temperature mark indicator:nth-child(even) { + color:transparent; +} + +.night-light-temperature trough { + padding-top: 2px; + padding-bottom: 2px; + background-image: linear-gradient(to right, mix(@bg_color, @ORANGE_100, 0.5), @ORANGE_500); +} + +.night-light-temperature:dir(rtl) trough { + background-image: linear-gradient(to left, mix(@bg_color, @ORANGE_100, 0.5), @ORANGE_500); +} + +.padded-spinbutton { + min-width: 40px; +} + +.unpadded-button { + padding: 6px; +} |