summaryrefslogtreecommitdiffstats
path: root/panels/display
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:45:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:45:20 +0000
commitae1c76ff830d146d41e88d6fba724c0a54bce868 (patch)
tree3c354bec95af07be35fc71a4b738268496f1a1c4 /panels/display
parentInitial commit. (diff)
downloadgnome-control-center-ae1c76ff830d146d41e88d6fba724c0a54bce868.tar.xz
gnome-control-center-ae1c76ff830d146d41e88d6fba724c0a54bce868.zip
Adding upstream version 1:43.6.upstream/1%43.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'panels/display')
-rw-r--r--panels/display/TODO837
-rw-r--r--panels/display/cc-display-arrangement.c978
-rw-r--r--panels/display/cc-display-arrangement.h47
-rw-r--r--panels/display/cc-display-config-dbus.c2116
-rw-r--r--panels/display/cc-display-config-dbus.h40
-rw-r--r--panels/display/cc-display-config-manager-dbus.c241
-rw-r--r--panels/display/cc-display-config-manager-dbus.h34
-rw-r--r--panels/display/cc-display-config-manager.c73
-rw-r--r--panels/display/cc-display-config-manager.h49
-rw-r--r--panels/display/cc-display-config.c665
-rw-r--r--panels/display/cc-display-config.h272
-rw-r--r--panels/display/cc-display-panel.c1140
-rw-r--r--panels/display/cc-display-panel.h30
-rw-r--r--panels/display/cc-display-panel.ui316
-rw-r--r--panels/display/cc-display-settings.c919
-rw-r--r--panels/display/cc-display-settings.h48
-rw-r--r--panels/display/cc-display-settings.ui101
-rw-r--r--panels/display/cc-night-light-page.c746
-rw-r--r--panels/display/cc-night-light-page.h32
-rw-r--r--panels/display/cc-night-light-page.ui438
-rw-r--r--panels/display/display-arrangement.css33
-rw-r--r--panels/display/display.gresource.xml10
-rw-r--r--panels/display/gnome-display-panel.desktop.in.in18
-rw-r--r--panels/display/icons/meson.build4
-rw-r--r--panels/display/icons/scalable/org.gnome.Settings-display-symbolic.svg4
-rw-r--r--panels/display/meson.build64
-rw-r--r--panels/display/night-light.css28
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;
+}