summaryrefslogtreecommitdiffstats
path: root/panels/display
diff options
context:
space:
mode:
Diffstat (limited to 'panels/display')
-rw-r--r--panels/display/TODO837
-rw-r--r--panels/display/cc-display-arrangement.c988
-rw-r--r--panels/display/cc-display-arrangement.h47
-rw-r--r--panels/display/cc-display-config-dbus.c1792
-rw-r--r--panels/display/cc-display-config-dbus.h40
-rw-r--r--panels/display/cc-display-config-manager-dbus.c188
-rw-r--r--panels/display/cc-display-config-manager-dbus.h34
-rw-r--r--panels/display/cc-display-config-manager.c61
-rw-r--r--panels/display/cc-display-config-manager.h43
-rw-r--r--panels/display/cc-display-config.c640
-rw-r--r--panels/display/cc-display-config.h253
-rw-r--r--panels/display/cc-display-panel.c1183
-rw-r--r--panels/display/cc-display-panel.h30
-rw-r--r--panels/display/cc-display-panel.ui458
-rw-r--r--panels/display/cc-display-settings.c798
-rw-r--r--panels/display/cc-display-settings.h44
-rw-r--r--panels/display/cc-display-settings.ui72
-rw-r--r--panels/display/cc-night-light-page.c712
-rw-r--r--panels/display/cc-night-light-page.h32
-rw-r--r--panels/display/cc-night-light-page.ui443
-rw-r--r--panels/display/display-arrangement.css26
-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/16x16/preferences-desktop-display.pngbin0 -> 613 bytes
-rw-r--r--panels/display/icons/22x22/preferences-desktop-display.pngbin0 -> 866 bytes
-rw-r--r--panels/display/icons/24x24/preferences-desktop-display.pngbin0 -> 909 bytes
-rw-r--r--panels/display/icons/32x32/preferences-desktop-display.pngbin0 -> 1602 bytes
-rw-r--r--panels/display/icons/meson.build18
-rw-r--r--panels/display/icons/scalable/preferences-desktop-display.svg470
-rw-r--r--panels/display/meson.build65
-rw-r--r--panels/display/night-light.css28
31 files changed, 9330 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..adbbcbc
--- /dev/null
+++ b/panels/display/cc-display-arrangement.c
@@ -0,0 +1,988 @@
+/* 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
+cc_display_arrangement_update_cursor (CcDisplayArrangement *self,
+ gboolean dragable)
+{
+ g_autoptr(GdkCursor) cursor = NULL;
+ GdkWindow *window;
+
+ if (dragable)
+ cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (self)), GDK_FLEUR);
+ else
+ cursor = NULL;
+
+ window = gtk_widget_get_window (GTK_WIDGET (self));
+
+ if (window)
+ gdk_window_set_cursor (window, cursor);
+}
+
+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 gboolean
+cc_display_arrangement_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (widget);
+ GtkStyleContext *context = gtk_widget_get_style_context (widget);
+ g_autoptr(GList) outputs = NULL;
+ GList *l;
+
+ if (!self->config)
+ return FALSE;
+
+ cc_display_arrangement_update_matrices (self);
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, "display-arrangement");
+
+ /* 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, state, &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, state, &border);
+ gtk_style_context_get_padding (context, state, &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;
+ PangoFontDescription *font = NULL;
+ 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, state, &border);
+ gtk_style_context_get_padding (context, state, &padding);
+ gtk_style_context_get_margin (context, state, &margin);
+
+ cairo_translate (cr, margin.left, margin.top);
+
+ number_str = g_strdup_printf ("%d", num);
+ gtk_style_context_get (context, state, "font", &font, NULL);
+ layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), number_str);
+ pango_layout_set_font_description (layout, font);
+ 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;
+
+ 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, state, &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);
+ }
+
+ gtk_style_context_restore (context);
+
+ return TRUE;
+}
+
+static gboolean
+cc_display_arrangement_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (widget);
+ CcDisplayMonitor *output;
+ gdouble event_x, event_y;
+ gint mon_x, mon_y;
+
+ if (!self->config)
+ return FALSE;
+
+ /* Only handle normal presses of the left mouse button. */
+ if (event->button != 1 || event->type != GDK_BUTTON_PRESS)
+ return FALSE;
+
+ g_return_val_if_fail (self->drag_active == FALSE, FALSE);
+
+ output = cc_display_arrangement_find_monitor_at (self, event->x, event->y);
+ if (!output)
+ return FALSE;
+
+ event_x = event->x;
+ event_y = event->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
+cc_display_arrangement_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (widget);
+ CcDisplayMonitor *output;
+
+ if (!self->config)
+ return FALSE;
+
+ /* Only handle left mouse button */
+ if (event->button != 1)
+ return FALSE;
+
+ if (!self->drag_active)
+ return FALSE;
+
+ self->drag_active = FALSE;
+
+ output = cc_display_arrangement_find_monitor_at (self, event->x, event->y);
+ cc_display_arrangement_update_cursor (self, output != NULL);
+
+ /* And queue a redraw to recenter everything */
+ gtk_widget_queue_draw (widget);
+
+ g_signal_emit_by_name (G_OBJECT (widget), "updated");
+
+ return TRUE;
+}
+
+static gboolean
+cc_display_arrangement_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (widget);
+ 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, event->x, event->y);
+
+ cc_display_arrangement_update_cursor (self, output != NULL);
+ if (self->prelit_output != output)
+ gtk_widget_queue_draw (widget);
+
+ self->prelit_output = output;
+
+ return FALSE;
+ }
+
+ g_assert (self->selected_output);
+
+ event_x = event->x;
+ event_y = event->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);
+ GtkWidgetClass *widget_class = GTK_WIDGET_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;
+
+ widget_class->draw = cc_display_arrangement_draw;
+ widget_class->button_press_event = cc_display_arrangement_button_press_event;
+ widget_class->button_release_event = cc_display_arrangement_button_release_event;
+ widget_class->motion_notify_event = cc_display_arrangement_motion_notify_event;
+
+ 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);
+}
+
+static void
+cc_display_arrangement_init (CcDisplayArrangement *self)
+{
+ /* XXX: Do we need to listen to touch events here? */
+ gtk_widget_add_events (GTK_WIDGET (self),
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
+
+ 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..cdc8f8a
--- /dev/null
+++ b/panels/display/cc-display-config-dbus.c
@@ -0,0 +1,1792 @@
+/*
+ * 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 <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;
+
+ 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 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 const double *
+cc_display_mode_dbus_get_supported_scales (CcDisplayMode *pself)
+{
+ CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself);
+
+ return (const double *) self->supported_scales->data;
+}
+
+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++)
+ if (g_array_index (self->supported_scales, double, i) == scale)
+ 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 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 (TRUE, TRUE, 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->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->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 (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);
+
+ 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 &&
+ m1->scale == m2->scale &&
+ 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;
+ 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 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_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 (self->logical_monitor->scale != scale)
+ {
+ 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_foreach (self->modes, (GFunc) g_object_unref, NULL);
+ g_clear_pointer (&self->modes, g_list_free);
+
+ 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->set_mode = cc_display_monitor_dbus_set_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 (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);
+ }
+}
+
+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);
+ }
+ }
+
+ 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;
+
+ GList *clone_modes;
+};
+
+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 GList *
+cc_display_config_dbus_get_cloning_modes (CcDisplayConfig *pself)
+{
+ CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself);
+
+ return self->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_scaled_mode_allowed (CcDisplayConfigDBus *self,
+ CcDisplayMode *pmode,
+ double scale)
+{
+ gint width, height;
+ CcDisplayModeDBus *mode = CC_DISPLAY_MODE_DBUS (pmode);
+
+ if (!cc_display_mode_dbus_is_supported_scale (pmode, scale))
+ return FALSE;
+
+ /* Do the math as if the monitor is always in landscape mode. */
+ width = round (mode->width / scale);
+ height = round (mode->height / scale);
+
+ return (MAX (width, height) >= self->min_width &&
+ MIN (width, height) >= self->min_height);
+}
+
+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 (!is_scaled_mode_allowed (self, mode, scale))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+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);
+
+ self->min_width = width;
+ self->min_height = height;
+}
+
+static gboolean
+cc_display_config_dbus_is_scaled_mode_valid (CcDisplayConfig *pself,
+ CcDisplayMode *mode,
+ double scale)
+{
+ CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself);
+
+ if (self->global_scale_required || cc_display_config_is_cloning (pself))
+ return is_scale_allowed_by_active_monitors (self, mode, scale);
+
+ return is_scaled_mode_allowed (self, 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
+gather_clone_modes (CcDisplayConfigDBus *self)
+{
+ guint n_monitors = g_list_length (self->monitors);
+ CcDisplayMonitorDBus *monitor;
+ GList *l;
+
+ if (n_monitors < 2)
+ return;
+
+ monitor = self->monitors->data;
+ for (l = monitor->modes; l != NULL; l = l->next)
+ {
+ CcDisplayModeDBus *mode = l->data;
+ gboolean valid = TRUE;
+ GList *ll;
+ for (ll = self->monitors->next; ll != NULL; ll = ll->next)
+ {
+ CcDisplayMonitorDBus *other_monitor = ll->data;
+ if (!cc_display_monitor_dbus_get_closest_mode (other_monitor, mode))
+ {
+ valid = FALSE;
+ break;
+ }
+ }
+ if (valid)
+ self->clone_modes = g_list_prepend (self->clone_modes, mode);
+ }
+}
+
+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);
+ }
+
+ 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);
+ }
+
+ gather_clone_modes (self);
+}
+
+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);
+
+ 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_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_list_foreach (self->monitors, (GFunc) g_object_unref, NULL);
+ g_clear_pointer (&self->monitors, g_list_free);
+ g_clear_pointer (&self->logical_monitors, g_hash_table_destroy);
+ g_clear_pointer (&self->clone_modes, g_list_free);
+
+ 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->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->get_cloning_modes = cc_display_config_dbus_get_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..653bea0
--- /dev/null
+++ b/panels/display/cc-display-config-manager-dbus.c
@@ -0,0 +1,188 @@
+/*
+ * 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;
+};
+
+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;
+
+ 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);
+ get_current_state (self);
+}
+
+static void
+cc_display_config_manager_dbus_init (CcDisplayConfigManagerDBus *self)
+{
+ 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 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;
+}
+
+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..0da298a
--- /dev/null
+++ b/panels/display/cc-display-config-manager.c
@@ -0,0 +1,61 @@
+/*
+ * 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);
+}
diff --git a/panels/display/cc-display-config-manager.h b/panels/display/cc-display-config-manager.h
new file mode 100644
index 0000000..1e1b363
--- /dev/null
+++ b/panels/display/cc-display-config-manager.h
@@ -0,0 +1,43 @@
+/*
+ * 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);
+};
+
+CcDisplayConfig * cc_display_config_manager_get_current (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..08b4c48
--- /dev/null
+++ b/panels/display/cc-display-config.c
@@ -0,0 +1,640 @@
+/*
+ * 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)
+{
+}
+
+void
+cc_display_mode_get_resolution (CcDisplayMode *self, int *w, int *h)
+{
+ return CC_DISPLAY_MODE_GET_CLASS (self)->get_resolution (self, w, h);
+}
+
+const double *
+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);
+}
+
+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);
+}
+
+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_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 *mode)
+{
+ GList *outputs, *l;
+
+ g_return_if_fail (CC_IS_DISPLAY_CONFIG (config));
+
+ outputs = cc_display_config_get_monitors (config);
+ for (l = outputs; l; l = l->next)
+ {
+ CcDisplayMonitor *output = l->data;
+ cc_display_monitor_set_mode (output, 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_get_cloning_modes (CcDisplayConfig *self)
+{
+ g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), NULL);
+ return CC_DISPLAY_CONFIG_GET_CLASS (self)->get_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..fa5b3a5
--- /dev/null
+++ b/panels/display/cc-display-config.h
@@ -0,0 +1,253 @@
+/*
+ * 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;
+
+
+#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;
+
+ void (*get_resolution) (CcDisplayMode *self, int *w, int *h);
+ const double* (*get_supported_scales) (CcDisplayMode *self);
+ double (*get_preferred_scale) (CcDisplayMode *self);
+ gboolean (*is_interlaced) (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);
+ CcDisplayMode* (*get_mode) (CcDisplayMonitor *self);
+ CcDisplayMode* (*get_preferred_mode) (CcDisplayMonitor *self);
+ GList* (*get_modes) (CcDisplayMonitor *self);
+ 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* (*get_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_get_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);
+
+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_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);
+
+void cc_display_mode_get_resolution (CcDisplayMode *mode,
+ int *width,
+ int *height);
+const double* 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);
+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..f58aaa8
--- /dev/null
+++ b/panels/display/cc-display-panel.c
@@ -0,0 +1,1183 @@
+/*
+ * 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 <handy.h>
+
+#include "shell/cc-object-storage.h"
+#include "list-box-helper.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 740
+#define MINIMUM_HEIGHT 530
+
+#define PANEL_PADDING 32
+#define SECTION_PADDING 32
+#define HEADING_PADDING 12
+
+typedef enum {
+ CC_DISPLAY_CONFIG_SINGLE,
+ 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;
+ GtkDialog *night_light_dialog;
+
+ UpClient *up_client;
+ gboolean lid_is_closed;
+
+ GDBusProxy *shell_proxy;
+
+ gchar *main_title;
+ GtkWidget *main_titlebar;
+ GtkWidget *apply_titlebar;
+ GtkWidget *apply_titlebar_apply;
+ GtkWidget *apply_titlebar_warning;
+
+ GListStore *primary_display_list;
+ GtkListStore *output_selection_list;
+
+ GtkWidget *arrangement_frame;
+ GtkAlignment *arrangement_bin;
+ GtkRadioButton *config_type_join;
+ GtkRadioButton *config_type_mirror;
+ GtkRadioButton *config_type_single;
+ GtkWidget *config_type_switcher_frame;
+ GtkLabel *current_output_label;
+ GtkWidget *display_settings_frame;
+ GtkBox *multi_selection_box;
+ GtkSwitch *output_enabled_switch;
+ GtkComboBox *output_selection_combo;
+ GtkStack *output_selection_stack;
+ GtkButtonBox *output_selection_two_buttonbox;
+ GtkRadioButton *output_selection_two_first;
+ GtkRadioButton *output_selection_two_second;
+ HdyComboRow *primary_display_row;
+ GtkWidget *stack_switcher;
+};
+
+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 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 (n_active_outputs == 1)
+ return CC_DISPLAY_CONFIG_SINGLE;
+
+ 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 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_single)))
+ return CC_DISPLAY_CONFIG_SINGLE;
+ else
+ g_assert_not_reached ();
+}
+
+static void
+config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type)
+{
+ CcDisplayConfigType current_type = config_get_current_type (panel);
+ GList *outputs, *l;
+
+ /* 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);
+
+ switch (type)
+ {
+ case CC_DISPLAY_CONFIG_SINGLE:
+ g_debug ("Creating new single config");
+ /* Disable all but the current primary output */
+ cc_display_config_set_cloning (panel->current_config, FALSE);
+ for (l = outputs; l; l = l->next)
+ {
+ CcDisplayMonitor *output = l->data;
+
+ /* Select the current primary output as the active one */
+ if (cc_display_monitor_is_primary (output))
+ {
+ cc_display_monitor_set_active (output, TRUE);
+ cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output));
+ set_current_output (panel, output, FALSE);
+ }
+ else
+ {
+ cc_display_monitor_set_active (output, FALSE);
+ cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output));
+ }
+ }
+ break;
+
+ 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;
+
+ cc_display_monitor_set_active (output, cc_display_monitor_is_usable (output));
+ cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output));
+ }
+ break;
+
+ case CC_DISPLAY_CONFIG_CLONE:
+ {
+ g_debug ("Creating new clone config");
+ GList *modes = cc_display_config_get_cloning_modes (panel->current_config);
+ gint bw, bh;
+ CcDisplayMode *best = NULL;
+
+ /* Turn on cloning and select the best mode we can find by default */
+ cc_display_config_set_cloning (panel->current_config, TRUE);
+
+ while (modes)
+ {
+ CcDisplayMode *mode = modes->data;
+ gint w, h;
+
+ cc_display_mode_get_resolution (mode, &w, &h);
+ if (best == NULL || (bw*bh < w*h))
+ {
+ best = mode;
+ cc_display_mode_get_resolution (best, &bw, &bh);
+ }
+
+ modes = modes->next;
+ }
+ cc_display_config_set_mode_on_all_outputs (panel->current_config, best);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ 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;
+ case CC_DISPLAY_CONFIG_SINGLE:
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_single), 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)
+ 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_has_toplevel_focus (GTK_WINDOW (w->data)))
+ {
+ monitor_labeler_show (self);
+ break;
+ }
+ }
+
+ if (!w)
+ monitor_labeler_hide (self);
+}
+
+static void
+dialog_toplevel_focus_changed (CcDisplayPanel *self)
+{
+ ensure_monitor_labels (self);
+}
+
+static void
+reset_titlebar (CcDisplayPanel *self)
+{
+ GtkWidget *toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)));
+
+ if (self->main_titlebar)
+ {
+ gtk_window_set_titlebar (GTK_WINDOW (toplevel), self->main_titlebar);
+ g_clear_object (&self->main_titlebar);
+
+ /* The split header bar will not reset the window title, so do that here. */
+ gtk_window_set_title (GTK_WINDOW (toplevel), self->main_title);
+ g_clear_pointer (&self->main_title, g_free);
+ }
+
+ g_clear_object (&self->apply_titlebar);
+ g_clear_object (&self->apply_titlebar_apply);
+ g_clear_object (&self->apply_titlebar_warning);
+}
+
+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);
+
+ reset_titlebar (CC_DISPLAY_PANEL (object));
+
+ if (self->focus_id)
+ {
+ self->focus_id = 0;
+ monitor_labeler_hide (CC_DISPLAY_PANEL (object));
+ }
+
+ g_clear_object (&self->manager);
+ g_clear_object (&self->current_config);
+ g_clear_object (&self->up_client);
+
+ g_clear_object (&self->shell_proxy);
+
+ g_clear_pointer ((GtkWidget **) &self->night_light_dialog, gtk_widget_destroy);
+
+ 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_config_type_toggled_cb (CcDisplayPanel *panel,
+ GtkRadioButton *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_list_box_row_activated_cb (CcDisplayPanel *panel)
+{
+ GtkWindow *toplevel;
+ toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel))));
+
+ if (!panel->night_light_dialog)
+ {
+ GtkWidget *content_area;
+
+ panel->night_light_dialog = (GtkDialog *)gtk_dialog_new ();
+
+ content_area = gtk_dialog_get_content_area (panel->night_light_dialog);
+ gtk_container_add (GTK_CONTAINER (content_area),
+ GTK_WIDGET (panel->night_light_page));
+ gtk_widget_show (GTK_WIDGET (panel->night_light_page));
+ }
+
+ gtk_window_set_transient_for (GTK_WINDOW (panel->night_light_dialog), toplevel);
+ gtk_window_present (GTK_WINDOW (panel->night_light_dialog));
+}
+
+static void
+on_output_enabled_active_changed_cb (CcDisplayPanel *panel)
+{
+ gboolean active;
+
+ if (!panel->current_output)
+ return;
+
+ active = gtk_switch_get_active (panel->output_enabled_switch);
+
+ if (cc_display_monitor_is_active (panel->current_output) == active)
+ return;
+
+ cc_display_monitor_set_active (panel->current_output, active);
+
+ /* Prevent the invalid configuration of disabling the last monitor
+ * by switching on a different one. */
+ if (config_get_current_type (panel) == CC_DISPLAY_CONFIG_INVALID_NONE)
+ {
+ GList *outputs, *l;
+
+ outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config);
+ for (l = outputs; l; l = l->next)
+ {
+ CcDisplayMonitor *output = CC_DISPLAY_MONITOR (l->data);
+
+ if (output == panel->current_output)
+ continue;
+
+ if (!cc_display_monitor_is_usable (output))
+ continue;
+
+ cc_display_monitor_set_active (output, TRUE);
+ cc_display_monitor_set_primary (output, TRUE);
+ break;
+ }
+ }
+
+ /* Changing the active state requires a UI rebuild. */
+ rebuild_ui (panel);
+}
+
+static void
+on_output_selection_combo_changed_cb (CcDisplayPanel *panel)
+{
+ GtkTreeIter iter;
+ g_autoptr(CcDisplayMonitor) output = NULL;
+
+ if (!panel->current_config)
+ return;
+
+ if (!gtk_combo_box_get_active_iter (panel->output_selection_combo, &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter,
+ 1, &output,
+ -1);
+
+ set_current_output (panel, output, FALSE);
+}
+
+static void
+on_output_selection_two_toggled_cb (CcDisplayPanel *panel, GtkRadioButton *btn)
+{
+ CcDisplayMonitor *output;
+
+ if (panel->rebuilding_counter > 0)
+ return;
+
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn)))
+ return;
+
+ output = g_object_get_data (G_OBJECT (btn), "display");
+
+ /* Stay in single mode when we are in single mode.
+ * This UI must never cause a switch between the configuration type.
+ * this is in contrast to the combobox monitor selection, which may
+ * switch to a disabled output both in SINGLE/MULTI mode without
+ * anything changing.
+ */
+ if (cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_SINGLE)
+ {
+ if (panel->current_output)
+ cc_display_monitor_set_active (panel->current_output, FALSE);
+ if (output)
+ cc_display_monitor_set_active (output, TRUE);
+
+ update_apply_button (panel);
+ }
+
+ set_current_output (panel, g_object_get_data (G_OBJECT (btn), "display"), FALSE);
+}
+
+static void
+on_primary_display_selected_index_changed_cb (CcDisplayPanel *panel)
+{
+ gint idx = hdy_combo_row_get_selected_index (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
+cc_display_panel_constructed (GObject *object)
+{
+ g_signal_connect_object (cc_panel_get_shell (CC_PANEL (object)), "notify::active-panel",
+ G_CALLBACK (active_panel_changed), object, G_CONNECT_SWAPPED);
+
+ 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 GtkWidget *
+cc_display_panel_get_title_widget (CcPanel *panel)
+{
+ CcDisplayPanel *self = CC_DISPLAY_PANEL (panel);
+
+ return self->stack_switcher;
+}
+
+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;
+ panel_class->get_title_widget = cc_display_panel_get_title_widget;
+
+ 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, arrangement_frame);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_bin);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_switcher_frame);
+ 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, config_type_single);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, current_output_label);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_frame);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, multi_selection_box);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, night_light_page);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_enabled_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_combo);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_stack);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_buttonbox);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_first);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_second);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, primary_display_row);
+ gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, stack_switcher);
+
+ gtk_widget_class_bind_template_callback (widget_class, on_config_type_toggled_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_night_light_list_box_row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_output_enabled_active_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_output_selection_combo_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_output_selection_two_toggled_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_primary_display_selected_index_changed_cb);
+}
+
+static void
+set_current_output (CcDisplayPanel *panel,
+ CcDisplayMonitor *output,
+ gboolean force)
+{
+ GtkTreeIter iter;
+ 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 (panel->current_output)
+ {
+ gtk_label_set_text (panel->current_output_label, cc_display_monitor_get_ui_name (panel->current_output));
+ gtk_switch_set_active (panel->output_enabled_switch, cc_display_monitor_is_active (panel->current_output));
+ gtk_widget_set_sensitive (GTK_WIDGET (panel->output_enabled_switch), cc_display_monitor_is_usable (panel->current_output));
+ }
+ else
+ {
+ gtk_label_set_text (panel->current_output_label, "");
+ gtk_switch_set_active (panel->output_enabled_switch, FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (panel->output_enabled_switch), FALSE);
+ }
+
+ if (g_object_get_data (G_OBJECT (panel->output_selection_two_first), "display") == output)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->output_selection_two_first), TRUE);
+ if (g_object_get_data (G_OBJECT (panel->output_selection_two_second), "display") == output)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->output_selection_two_second), TRUE);
+
+ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (panel->output_selection_list), &iter);
+ while (gtk_list_store_iter_is_valid (panel->output_selection_list, &iter))
+ {
+ g_autoptr(CcDisplayMonitor) o = NULL;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter,
+ 1, &o,
+ -1);
+
+ if (o == panel->current_output)
+ {
+ gtk_combo_box_set_active_iter (panel->output_selection_combo, &iter);
+ break;
+ }
+
+ gtk_tree_model_iter_next (GTK_TREE_MODEL (panel->output_selection_list), &iter);
+ }
+
+ if (changed)
+ {
+ cc_display_settings_set_selected_output (panel->settings, panel->current_output);
+ cc_display_arrangement_set_selected_output (panel->arrangement, panel->current_output);
+ }
+
+ panel->rebuilding_counter--;
+}
+
+static void
+rebuild_ui (CcDisplayPanel *panel)
+{
+ guint n_outputs, n_active_outputs, n_usable_outputs;
+ GList *outputs, *l;
+ CcDisplayConfigType type;
+
+ panel->rebuilding_counter++;
+
+ g_list_store_remove_all (panel->primary_display_list);
+ gtk_list_store_clear (panel->output_selection_list);
+
+ if (!panel->current_config)
+ {
+ panel->rebuilding_counter--;
+ return;
+ }
+
+ 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)
+ {
+ GtkTreeIter iter;
+ CcDisplayMonitor *output = l->data;
+
+ gtk_list_store_append (panel->output_selection_list, &iter);
+ gtk_list_store_set (panel->output_selection_list,
+ &iter,
+ 0, cc_display_monitor_get_ui_number_name (output),
+ 1, output,
+ -1);
+
+ if (!cc_display_monitor_is_usable (output))
+ continue;
+
+ n_usable_outputs += 1;
+
+ if (n_usable_outputs == 1)
+ {
+ gtk_button_set_label (GTK_BUTTON (panel->output_selection_two_first),
+ cc_display_monitor_get_ui_name (output));
+ g_object_set_data (G_OBJECT (panel->output_selection_two_first),
+ "display",
+ output);
+ }
+ else if (n_usable_outputs == 2)
+ {
+ gtk_button_set_label (GTK_BUTTON (panel->output_selection_two_second),
+ cc_display_monitor_get_ui_name (output));
+ g_object_set_data (G_OBJECT (panel->output_selection_two_second),
+ "display",
+ output);
+ }
+
+ 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))
+ hdy_combo_row_set_selected_index (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);
+ }
+ }
+
+ /* 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->config_type_switcher_frame, TRUE);
+ gtk_widget_set_visible (panel->arrangement_frame, type == CC_DISPLAY_CONFIG_JOIN);
+
+ /* We need a switcher except in CLONE mode */
+ if (type == CC_DISPLAY_CONFIG_CLONE)
+ gtk_stack_set_visible_child (panel->output_selection_stack, GTK_WIDGET (panel->current_output_label));
+ else
+ gtk_stack_set_visible_child (panel->output_selection_stack, GTK_WIDGET (panel->output_selection_two_buttonbox));
+ }
+ 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 even if we are in SINGLE mode.
+ */
+ gtk_widget_set_visible (panel->config_type_switcher_frame, FALSE);
+ gtk_widget_set_visible (panel->arrangement_frame, 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;
+
+ gtk_stack_set_visible_child (panel->output_selection_stack, GTK_WIDGET (panel->multi_selection_box));
+ }
+ else
+ {
+ /* We only have a single usable monitor, show neither configuration type
+ * switcher nor arrangement widget and ensure we really are in SINGLE
+ * mode (and not e.g. mirroring across one display) */
+ type = CC_DISPLAY_CONFIG_SINGLE;
+
+ gtk_widget_set_visible (panel->config_type_switcher_frame, FALSE);
+ gtk_widget_set_visible (panel->arrangement_frame, FALSE);
+
+ gtk_stack_set_visible_child (panel->output_selection_stack, GTK_WIDGET (panel->current_output_label));
+ }
+
+ 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);
+ gtk_list_store_clear (panel->output_selection_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 gboolean
+on_toplevel_key_press (GtkWidget *button,
+ GdkEventKey *event)
+{
+ if (event->keyval != GDK_KEY_Escape)
+ return GDK_EVENT_PROPAGATE;
+
+ g_signal_emit_by_name (button, "activate");
+ return GDK_EVENT_STOP;
+}
+
+static void
+show_apply_titlebar (CcDisplayPanel *panel, gboolean is_applicable)
+{
+ if (!panel->apply_titlebar)
+ {
+ g_autoptr(GtkSizeGroup) size_group = NULL;
+ GtkWidget *header, *button, *toplevel;
+ panel->apply_titlebar = header = gtk_header_bar_new ();
+ gtk_widget_show (header);
+
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+
+ button = gtk_button_new_with_mnemonic (_("_Cancel"));
+ gtk_widget_show (button);
+ gtk_header_bar_pack_start (GTK_HEADER_BAR (header), button);
+ gtk_size_group_add_widget (size_group, button);
+ g_signal_connect_object (button, "clicked", G_CALLBACK (on_screen_changed),
+ panel, G_CONNECT_SWAPPED);
+
+ toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)));
+ g_signal_connect_object (toplevel, "key-press-event", G_CALLBACK (on_toplevel_key_press),
+ button, G_CONNECT_SWAPPED);
+
+ panel->apply_titlebar_apply = button = gtk_button_new_with_mnemonic (_("_Apply"));
+ gtk_widget_show (button);
+ gtk_header_bar_pack_end (GTK_HEADER_BAR (header), button);
+ gtk_size_group_add_widget (size_group, button);
+ g_signal_connect_object (button, "clicked", G_CALLBACK (apply_current_configuration),
+ panel, G_CONNECT_SWAPPED);
+ gtk_style_context_add_class (gtk_widget_get_style_context (button),
+ GTK_STYLE_CLASS_SUGGESTED_ACTION);
+
+ header = gtk_window_get_titlebar (GTK_WINDOW (toplevel));
+ if (header)
+ panel->main_titlebar = g_object_ref (header);
+ panel->main_title = g_strdup (gtk_window_get_title (GTK_WINDOW (toplevel)));
+
+ gtk_window_set_titlebar (GTK_WINDOW (toplevel), panel->apply_titlebar);
+ g_object_ref (panel->apply_titlebar);
+ g_object_ref (panel->apply_titlebar_apply);
+ }
+
+ if (is_applicable)
+ {
+ gtk_header_bar_set_title (GTK_HEADER_BAR (panel->apply_titlebar), _("Apply Changes?"));
+ gtk_header_bar_set_subtitle (GTK_HEADER_BAR (panel->apply_titlebar), NULL);
+ }
+ else
+ {
+ gtk_header_bar_set_title (GTK_HEADER_BAR (panel->apply_titlebar), _("Changes Cannot be Applied"));
+ gtk_header_bar_set_subtitle (GTK_HEADER_BAR (panel->apply_titlebar), _("This could be due to hardware limitations."));
+ }
+ gtk_widget_set_sensitive (panel->apply_titlebar_apply, is_applicable);
+}
+
+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);
+}
+
+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::has-toplevel-focus",
+ G_CALLBACK (dialog_toplevel_focus_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;
+ GtkCellRenderer *renderer;
+
+ g_resources_register (cc_display_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->arrangement = cc_display_arrangement_new (NULL);
+
+ gtk_widget_show (GTK_WIDGET (self->arrangement));
+ gtk_widget_set_size_request (GTK_WIDGET (self->arrangement), 400, 175);
+ gtk_container_add (GTK_CONTAINER (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 ();
+ gtk_widget_show (GTK_WIDGET (self->settings));
+ gtk_container_add (GTK_CONTAINER (self->display_settings_frame), 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);
+ hdy_combo_row_bind_name_model (self->primary_display_row,
+ G_LIST_MODEL (self->primary_display_list),
+ (HdyComboRowGetNameFunc) cc_display_monitor_dup_ui_number_name,
+ NULL, NULL);
+
+ self->output_selection_list = gtk_list_store_new (2, G_TYPE_STRING, CC_TYPE_DISPLAY_MONITOR);
+ gtk_combo_box_set_model (self->output_selection_combo, GTK_TREE_MODEL (self->output_selection_list));
+ gtk_cell_layout_clear (GTK_CELL_LAYOUT (self->output_selection_combo));
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->output_selection_combo),
+ renderer,
+ TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (self->output_selection_combo),
+ renderer,
+ "text",
+ 0);
+ gtk_cell_renderer_set_visible (renderer, TRUE);
+
+ 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_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
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..855b348
--- /dev/null
+++ b/panels/display/cc-display-panel.ui
@@ -0,0 +1,458 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <requires lib="libhandy" version="0.0"/>
+ <object class="GtkImage" id="image-join-displays">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="icon_name">video-joined-displays-symbolic</property>
+ <property name="icon_size">3</property>
+ </object>
+ <object class="GtkImage" id="image-mirror">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="icon_name">view-mirror-symbolic</property>
+ <property name="icon_size">3</property>
+ </object>
+ <object class="GtkImage" id="image-single-display">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="icon_name">video-single-display-symbolic</property>
+ <property name="icon_size">3</property>
+ </object>
+ <object class="GtkStackSwitcher" id="stack_switcher">
+ <property name="visible">True</property>
+ <property name="stack">stack</property>
+ </object>
+ <template class="CcDisplayPanel" parent="CcPanel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="transition-type">crossfade</property>
+
+ <!-- Displays page -->
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hscrollbar_policy">never</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="margin_top">32</property>
+ <property name="margin_bottom">32</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">12</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">32</property>
+ <child>
+ <object class="GtkFrame" id="config_type_switcher_frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkButtonBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <property name="layout_style">expand</property>
+ <child>
+ <object class="GtkRadioButton" id="config_type_single">
+ <property name="label" translatable="yes">Single Display</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="image">image-single-display</property>
+ <property name="always_show_image">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">config_type_join</property>
+ <signal name="toggled" handler="on_config_type_toggled_cb" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="config_type_join">
+ <property name="label" translatable="yes">Join Displays</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="image">image-join-displays</property>
+ <property name="always_show_image">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ <signal name="toggled" handler="on_config_type_toggled_cb" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="config_type_mirror">
+ <property name="label" translatable="yes">Mirror</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="image">image-mirror</property>
+ <property name="always_show_image">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">config_type_join</property>
+ <signal name="toggled" handler="on_config_type_toggled_cb" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_bottom">12</property>
+ <property name="label" translatable="yes">Display Mode</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="arrangement_frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">1</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkAlignment" id="arrangement_bin">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="label_item">
+ <placeholder/>
+ </child>
+ <style>
+ <class name="view"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkListBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">none</property>
+ <child>
+ <object class="HdyComboRow" id="primary_display_row">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="subtitle" translatable="yes">Contains top bar and Activities</property>
+ <property name="title" translatable="yes">Primary Display</property>
+ <signal name="notify::selected-index" handler="on_primary_display_selected_index_changed_cb" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label_item">
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Drag displays to match your physical display setup. Select a display to change its settings.</property>
+ <property name="wrap">True</property>
+ <property name="margin_bottom">12</property>
+ </object>
+ </child>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="frame_arrangement-atkobject">
+ <property name="AtkObject::accessible-name" translatable="yes">Display Arrangement</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkStack" id="output_selection_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hhomogeneous">False</property>
+ <property name="vhomogeneous">False</property>
+ <child>
+ <object class="GtkLabel" id="current_output_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="output_selection_two_buttonbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">expand</property>
+ <child>
+ <object class="GtkRadioButton" id="output_selection_two_first">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">output_selection_two_second</property>
+ <signal name="toggled" handler="on_output_selection_two_toggled_cb" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="output_selection_two_second">
+ <property name="label" translatable="yes">Join Displays</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">False</property>
+ <signal name="toggled" handler="on_output_selection_two_toggled_cb" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="multi_selection_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkComboBox" id="output_selection_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <signal name="changed" handler="on_output_selection_combo_changed_cb" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="output_enabled_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <signal name="notify::active" handler="on_output_enabled_active_changed_cb" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkListBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">none</property>
+ <child>
+ <object class="HdyComboRow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="title" translatable="yes">Active Display</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label_item">
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="name">single-select</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="display_settings_frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="label_item">
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label_item">
+ <placeholder/>
+ </child>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="AtkObject::accessible-name" translatable="yes">Display Configuration</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">displays</property>
+ <property name="title" translatable="yes">Displays</property>
+ </packing>
+ </child>
+
+ <!-- Night Light page -->
+ <child>
+ <object class="CcNightLightPage" id="night_light_page">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="name">night-light</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>
+ </packing>
+ </child>
+
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/display/cc-display-settings.c b/panels/display/cc-display-settings.c
new file mode 100644
index 0000000..49ddcf0
--- /dev/null
+++ b/panels/display/cc-display-settings.c
@@ -0,0 +1,798 @@
+/* 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 <handy.h>
+#include <glib/gi18n.h>
+#include <math.h>
+#include "list-box-helper.h"
+#include "cc-display-settings.h"
+#include "cc-display-config.h"
+
+#define MAX_SCALE_BUTTONS 5
+
+struct _CcDisplaySettings
+{
+ GtkDrawingArea object;
+
+ gboolean updating;
+ guint idle_udpate_id;
+
+ gboolean has_accelerometer;
+ CcDisplayConfig *config;
+ CcDisplayMonitor *selected_output;
+
+ GListStore *orientation_list;
+ GListStore *refresh_rate_list;
+ GListStore *resolution_list;
+
+ GtkWidget *orientation_row;
+ GtkWidget *refresh_rate_row;
+ GtkWidget *resolution_row;
+ GtkWidget *scale_bbox;
+ GtkWidget *scale_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_LIST_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 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 char *
+make_resolution_string (CcDisplayMode *mode)
+{
+ const char *interlaced = cc_display_mode_is_interlaced (mode) ? "i" : "";
+ const char *aspect;
+ int width, height;
+
+ cc_display_mode_get_resolution (mode, &width, &height);
+ aspect = make_aspect_string (width, height);
+
+ 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 gchar *
+get_frequency_string (CcDisplayMode *mode)
+{
+ return g_strdup_printf (_("%.2lf Hz"), cc_display_mode_get_freq_f (mode));
+}
+
+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);
+
+ /* Prefer wide screen if the size is equal */
+ res = wb*hb - wa*ha;
+ if (res == 0)
+ return wb - wa;
+ return res;
+}
+
+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)
+{
+ GList *modes;
+ GList *item;
+ gint width, height;
+ CcDisplayMode *current_mode;
+ GtkRadioButton *group = NULL;
+ gint buttons = 0;
+ const gdouble *scales, *scale;
+
+ self->idle_udpate_id = 0;
+
+ if (!self->config || !self->selected_output)
+ {
+ 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_row, FALSE);
+ gtk_widget_set_visible (self->underscanning_row, FALSE);
+
+ return G_SOURCE_REMOVE;
+ }
+
+ 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->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);
+ }
+
+ 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);
+
+ g_list_store_remove_all (self->orientation_list);
+ for (i = 0; i < G_N_ELEMENTS (rotations); i++)
+ {
+ g_autoptr(HdyValueObject) obj = NULL;
+
+ if (!cc_display_monitor_supports_rotation (self->selected_output, rotations[i]))
+ continue;
+
+ obj = hdy_value_object_new_collect (G_TYPE_STRING, string_for_rotation (rotations[i]));
+ g_list_store_append (self->orientation_list, obj);
+ 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])
+ hdy_combo_row_set_selected_index (HDY_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))
+ hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->refresh_rate_row), new);
+ }
+
+ /* Show if we have more than one frequency to choose from. */
+ gtk_widget_set_visible (self->refresh_rate_row,
+ g_list_model_get_n_items (G_LIST_MODEL (self->refresh_rate_list)) > 1);
+ }
+ 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))
+ modes = cc_display_config_get_cloning_modes (self->config);
+ 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);
+ hdy_combo_row_set_selected_index (HDY_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);
+
+ /* Exclude unusable low resolutions */
+ if (!cc_display_config_is_scaled_mode_valid (self->config, mode, 1.0))
+ continue;
+
+ 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. */
+ gtk_container_foreach (GTK_CONTAINER (self->scale_bbox), (GtkCallback) gtk_widget_destroy, NULL);
+ scales = cc_display_mode_get_supported_scales (current_mode);
+ for (scale = scales; *scale != 0.0; scale++)
+ {
+ g_autofree gchar *scale_str = NULL;
+ GtkWidget *scale_btn;
+
+ if (!cc_display_config_is_scaled_mode_valid (self->config,
+ current_mode,
+ *scale) &&
+ cc_display_monitor_get_scale (self->selected_output) != *scale)
+ continue;
+
+ scale_str = make_scale_string (*scale);
+
+ scale_btn = gtk_radio_button_new_with_label_from_widget (group, scale_str);
+ if (!group)
+ group = GTK_RADIO_BUTTON (scale_btn);
+ gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (scale_btn), FALSE);
+ g_object_set_data_full (G_OBJECT (scale_btn),
+ "scale",
+ g_memdup (scale, sizeof (gdouble)),
+ g_free);
+ gtk_widget_show (scale_btn);
+ gtk_container_add (GTK_CONTAINER (self->scale_bbox), scale_btn);
+ g_signal_connect_object (scale_btn,
+ "notify::active",
+ G_CALLBACK (on_scale_btn_active_changed_cb),
+ self, 0);
+
+ if (cc_display_monitor_get_scale (self->selected_output) == *scale)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scale_btn), TRUE);
+
+ buttons += 1;
+ if (buttons >= MAX_SCALE_BUTTONS)
+ break;
+ }
+
+ gtk_widget_set_visible (self->scale_row, buttons > 1);
+
+ 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->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->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_orientation_selection_changed_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ CcDisplaySettings *self)
+{
+ gint idx;
+ g_autoptr(HdyValueObject) obj = NULL;
+
+ if (self->updating)
+ return;
+
+ idx = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->orientation_row));
+ obj = g_list_model_get_item (G_LIST_MODEL (self->orientation_list), idx);
+
+ 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 = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->refresh_rate_row));
+ mode = g_list_model_get_item (G_LIST_MODEL (self->refresh_rate_list), idx);
+
+ 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 = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->resolution_row));
+ mode = g_list_model_get_item (G_LIST_MODEL (self->resolution_list), idx);
+
+ /* 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_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);
+
+ 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, 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_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_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_underscanning_switch_active_changed_cb);
+}
+
+static void
+cc_display_settings_init (CcDisplaySettings *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self),
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ self->orientation_list = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
+ self->refresh_rate_list = g_list_store_new (CC_TYPE_DISPLAY_MODE);
+ self->resolution_list = g_list_store_new (CC_TYPE_DISPLAY_MODE);
+
+ self->updating = TRUE;
+
+ hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->orientation_row),
+ G_LIST_MODEL (self->orientation_list),
+ (HdyComboRowGetNameFunc) hdy_value_object_dup_string,
+ NULL, NULL);
+ hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->refresh_rate_row),
+ G_LIST_MODEL (self->refresh_rate_list),
+ (HdyComboRowGetNameFunc) get_frequency_string,
+ NULL, NULL);
+ hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->resolution_row),
+ G_LIST_MODEL (self->resolution_list),
+ (HdyComboRowGetNameFunc) make_resolution_string,
+ NULL, NULL);
+
+ 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]);
+}
+
diff --git a/panels/display/cc-display-settings.h b/panels/display/cc-display-settings.h
new file mode 100644
index 0000000..58709dd
--- /dev/null
+++ b/panels/display/cc-display-settings.h
@@ -0,0 +1,44 @@
+/* -*- 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 <gtk/gtk.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, GtkListBox);
+
+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);
+
+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..7715ba3
--- /dev/null
+++ b/panels/display/cc-display-settings.ui
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <requires lib="libhandy" version="0.0"/>
+ <template class="CcDisplaySettings" parent="GtkListBox">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="selection_mode">none</property>
+ <child>
+ <object class="HdyComboRow" id="orientation_row">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="title" translatable="yes" context="display setting">Orientation</property>
+ <signal name="notify::selected-index" handler="on_orientation_selection_changed_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="HdyComboRow" id="resolution_row">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="title" translatable="yes" context="display setting">Resolution</property>
+ <signal name="notify::selected-index" handler="on_resolution_selection_changed_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="HdyComboRow" id="refresh_rate_row">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="title" translatable="yes">Refresh Rate</property>
+ <signal name="notify::selected-index" handler="on_refresh_rate_selection_changed_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="HdyActionRow" id="underscanning_row">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="title" translatable="yes">Adjust for TV</property>
+ <child>
+ <object class="GtkSwitch" id="underscanning_switch">
+ <property name="visible">True</property>
+ <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="HdyActionRow" id="scale_row">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="title" translatable="yes" context="display setting">Scale</property>
+ <child>
+ <object class="GtkButtonBox" id="scale_bbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="layout_style">expand</property>
+ </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..f51b0ba
--- /dev/null
+++ b/panels/display/cc-night-light-page.c
@@ -0,0 +1,712 @@
+/*
+ * 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 "list-box-helper.h"
+
+#include "shell/cc-object-storage.h"
+
+struct _CcNightLightPage {
+ GtkBin parent;
+
+ GtkWidget *box_manual;
+ GtkButton *button_from_am;
+ GtkButton *button_from_pm;
+ GtkButton *button_to_am;
+ GtkButton *button_to_pm;
+ GtkWidget *infobar_disabled;
+ GtkListBox *listbox;
+ GtkWidget *scale_color_temperature;
+ GtkWidget *night_light_toggle_switch;
+ GtkComboBox *schedule_type_combo;
+ GtkWidget *spinbutton_from_hours;
+ GtkWidget *spinbutton_from_minutes;
+ 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;
+};
+
+G_DEFINE_TYPE (CcNightLightPage, cc_night_light_page, GTK_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)
+{
+ 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;
+}
+
+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_entry_set_text (GTK_ENTRY (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_entry_set_text (GTK_ENTRY (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);
+}
+
+/* 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, 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_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, spinbutton_from_hours);
+ gtk_widget_class_bind_template_child (widget_class, CcNightLightPage, spinbutton_from_minutes);
+ 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_list_box_set_header_func (self->listbox, cc_list_box_update_header_func, NULL, NULL);
+
+ gtk_scale_add_mark (GTK_SCALE (self->scale_color_temperature),
+ 1700, GTK_POS_BOTTOM,
+ _("More Warm"));
+
+ 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,
+ _("Less Warm"));
+
+ 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_screen (gdk_screen_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);
+
+ 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..79422bb
--- /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 <gtk/gtk.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, GtkBin)
+
+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..02b14f7
--- /dev/null
+++ b/panels/display/cc-night-light-page.ui
@@ -0,0 +1,443 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcNightLightPage" parent="GtkBin">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">0</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_undisable">
+ <property name="label" translatable="yes" comments="This cancels the redshift inhibit.">Restart Filter</property>
+ <property name="name">button_undisable</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="dialog_undisable_clicked_cb" object="CcNightLightPage" swapped="no" />
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="spacing">16</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_left">12</property>
+ <property name="hexpand">False</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>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkListBox" id="listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection-mode">none</property>
+
+ <!-- Night Light -->
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <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="visible">True</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Schedule -->
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <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="visible">True</property>
+ <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>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <!-- Time -->
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <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="visible">True</property>
+ <property name="can_focus">False</property>
+ <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="visible">True</property>
+ <property name="can_focus">False</property>
+ <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">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton_from_hours">
+ <property name="visible">True</property>
+ <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>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="from_h_spinbutton-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes">Hour</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">:</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton_from_minutes">
+ <property name="visible">True</property>
+ <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>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="from_m_spinbutton-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes">Minute</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack_from">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">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="visible">True</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="visible">True</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="visible">True</property>
+ <property name="can_focus">False</property>
+ <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">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton_to_hours">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_width_chars">2</property>
+ <property name="text">4</property>
+ <property name="input_purpose">number</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>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="to_h_spinbutton-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes">Hour</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">:</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton_to_minutes">
+ <property name="visible">True</property>
+ <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>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="to_m_spinbutton-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes">Minute</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack_to">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">False</property>
+ <child>
+ <object class="GtkButton" id="button_to_am">
+ <property name="label" translatable="yes">AM</property>
+ <property name="visible">True</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="visible">True</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="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Color Temperature</property>
+ <property name="sensitive" bind-source="night_light_toggle_switch" bind-property="active" bind-flags="default|sync-create" />
+
+ <child>
+ <object class="GtkScale" id="scale_color_temperature">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</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>
+ </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..90bce87
--- /dev/null
+++ b/panels/display/display-arrangement.css
@@ -0,0 +1,26 @@
+
+.display-arrangement.monitor {
+ border: solid 1px @borders;
+ margin: 0px 0px 1px 1px;
+ background: @theme_bg_color;
+ padding: 0.4em;
+}
+
+.display-arrangement.monitor.primary {
+ border-top: 0.4em solid #000000;
+}
+
+.display-arrangement.monitor:selected {
+ background: @theme_selected_bg_color;
+}
+
+.display-arrangement.monitor-label {
+ font-size: larger;
+ font-weight: bold;
+ border-radius: 0.3em;
+ padding-right: 0.2em;
+ padding-left: 0.2em;
+ color: #fff;
+ background: #000;
+}
+
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..9c4744d
--- /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=preferences-desktop-display
+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/16x16/preferences-desktop-display.png b/panels/display/icons/16x16/preferences-desktop-display.png
new file mode 100644
index 0000000..f996ddf
--- /dev/null
+++ b/panels/display/icons/16x16/preferences-desktop-display.png
Binary files differ
diff --git a/panels/display/icons/22x22/preferences-desktop-display.png b/panels/display/icons/22x22/preferences-desktop-display.png
new file mode 100644
index 0000000..cc47eec
--- /dev/null
+++ b/panels/display/icons/22x22/preferences-desktop-display.png
Binary files differ
diff --git a/panels/display/icons/24x24/preferences-desktop-display.png b/panels/display/icons/24x24/preferences-desktop-display.png
new file mode 100644
index 0000000..49b4e12
--- /dev/null
+++ b/panels/display/icons/24x24/preferences-desktop-display.png
Binary files differ
diff --git a/panels/display/icons/32x32/preferences-desktop-display.png b/panels/display/icons/32x32/preferences-desktop-display.png
new file mode 100644
index 0000000..95de3ea
--- /dev/null
+++ b/panels/display/icons/32x32/preferences-desktop-display.png
Binary files differ
diff --git a/panels/display/icons/meson.build b/panels/display/icons/meson.build
new file mode 100644
index 0000000..7cabe54
--- /dev/null
+++ b/panels/display/icons/meson.build
@@ -0,0 +1,18 @@
+icon_sizes = [
+ '16x16',
+ '22x22',
+ '24x24',
+ '32x32'
+]
+
+foreach icon_size: icon_sizes
+ install_data(
+ join_paths(icon_size, 'preferences-desktop-display.png'),
+ install_dir: join_paths(control_center_icondir, 'hicolor', icon_size, 'apps')
+ )
+endforeach
+
+install_data(
+ 'scalable/preferences-desktop-display.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps')
+)
diff --git a/panels/display/icons/scalable/preferences-desktop-display.svg b/panels/display/icons/scalable/preferences-desktop-display.svg
new file mode 100644
index 0000000..0679b6b
--- /dev/null
+++ b/panels/display/icons/scalable/preferences-desktop-display.svg
@@ -0,0 +1,470 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48.000000px"
+ height="48.000000px"
+ id="svg3304"
+ sodipodi:version="0.32"
+ inkscape:version="0.44+devel"
+ sodipodi:docbase="/home/jimmac/gfx/ximian/art/icons/control-center/scalable"
+ sodipodi:docname="change-resolution.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="true">
+ <defs
+ id="defs3306">
+ <linearGradient
+ id="linearGradient2804">
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="0"
+ id="stop2806" />
+ <stop
+ id="stop2812"
+ offset="0.5"
+ style="stop-color:black;stop-opacity:1;" />
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="1"
+ id="stop2808" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2804"
+ id="linearGradient2831"
+ gradientUnits="userSpaceOnUse"
+ x1="21.875"
+ y1="48.000977"
+ x2="21.875"
+ y2="40" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2781"
+ id="radialGradient2829"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2,0,0,0.8,36,8.8)"
+ cx="1"
+ cy="44"
+ fx="1"
+ fy="44"
+ r="5" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2781">
+ <stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0"
+ id="stop2783" />
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="1"
+ id="stop2785" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2781"
+ id="radialGradient2827"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2,0,0,0.8,-13,-79.2)"
+ cx="1"
+ cy="44"
+ fx="1"
+ fy="44"
+ r="5" />
+ <linearGradient
+ id="linearGradient5137">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop5139" />
+ <stop
+ style="stop-color:#e6e6e3;stop-opacity:1;"
+ offset="1"
+ id="stop5141" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient6240">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6242" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop6244" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient11400">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop11402" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop11404" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient11400"
+ id="linearGradient11406"
+ x1="23.154902"
+ y1="34.572548"
+ x2="23.529411"
+ y2="40.219608"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,0.7954955)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5137"
+ id="linearGradient5147"
+ gradientUnits="userSpaceOnUse"
+ x1="17.247635"
+ y1="6.3760414"
+ x2="39.904388"
+ y2="38.876041"
+ gradientTransform="translate(0,0.7954955)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5137"
+ id="linearGradient5223"
+ gradientUnits="userSpaceOnUse"
+ x1="31.743324"
+ y1="37.842293"
+ x2="31.86105"
+ y2="43.82579"
+ gradientTransform="translate(0,0.7954955)" />
+ <linearGradient
+ id="linearGradient7025"
+ inkscape:collect="always">
+ <stop
+ id="stop7027"
+ offset="0"
+ style="stop-color:#e6ce46;stop-opacity:1" />
+ <stop
+ id="stop7029"
+ offset="1"
+ style="stop-color:#d6ba1c;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient22122"
+ inkscape:collect="always">
+ <stop
+ id="stop22124"
+ offset="0"
+ style="stop-color:black;stop-opacity:1;" />
+ <stop
+ id="stop22126"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient22140">
+ <stop
+ id="stop22142"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" />
+ <stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop22148" />
+ <stop
+ id="stop22144"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient22122"
+ id="radialGradient4770"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,-1.000001,1.142856,0,-41.10259,45.50001)"
+ cx="7"
+ cy="39.464806"
+ fx="7"
+ fy="39.464806"
+ r="3.5" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient22122"
+ id="radialGradient4772"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0,-1.000001,1.142856,0,-89.10259,-31.49999)"
+ cx="7"
+ cy="39.464806"
+ fx="7"
+ fy="39.464806"
+ r="3.5" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient22140"
+ id="linearGradient4774"
+ gradientUnits="userSpaceOnUse"
+ x1="18.142136"
+ y1="35"
+ x2="18.142136"
+ y2="42.040661" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient7025"
+ id="linearGradient4776"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(53,1.428571)"
+ x1="13.630114"
+ y1="28.5"
+ x2="25.208096"
+ y2="41.180992" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6240"
+ id="linearGradient4778"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(7.843e-3,0.7954955)"
+ x1="20.156862"
+ y1="5.0996137"
+ x2="20.156862"
+ y2="26.039215" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#849193"
+ borderopacity="1.0000000"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="39.697787"
+ inkscape:cy="26.598514"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:showpageshadow="false"
+ showborder="true"
+ inkscape:window-width="923"
+ inkscape:window-height="937"
+ inkscape:window-x="2004"
+ inkscape:window-y="169"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:grid-points="true" />
+ <metadata
+ id="metadata3309">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Change Resolution</dc:title>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>display</rdf:li>
+ <rdf:li>resolution</rdf:li>
+ <rdf:li>video</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Andreas Nilsson
+Luca Ferretti &lt;elle.uca@libero.it&gt;</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <dc:date></dc:date>
+ <dc:source>http://www.gnome.org</dc:source>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/SourceCode" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <g
+ id="g2822"
+ style="opacity:0.3"
+ transform="matrix(0.9308511,0,0,1.037397,1.6941489,-1.795056)">
+ <rect
+ transform="scale(-1,-1)"
+ y="-48"
+ x="-11"
+ height="8"
+ width="10"
+ id="rect1892"
+ style="opacity:1;color:#000000;fill:url(#radialGradient2827);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000057;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ y="40"
+ x="38"
+ height="8"
+ width="10"
+ id="rect2789"
+ style="opacity:1;color:#000000;fill:url(#radialGradient2829);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000057;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ y="40"
+ x="11"
+ height="8"
+ width="27"
+ id="rect2793"
+ style="opacity:1;color:#000000;fill:url(#linearGradient2831);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000057;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ <rect
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="rect4784"
+ width="48"
+ height="48"
+ x="0"
+ y="0.79549509" />
+ <path
+ style="opacity:1;color:#000000;fill:url(#linearGradient5223);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ d="M 14.375479,36.328843 C 14.375479,36.328843 15.592355,41.263443 10.51915,41.309767 C 8.0888743,41.331672 8.5866723,45.344918 8.5866723,45.344918 L 39.433139,45.313545 C 39.433139,45.313545 39.851577,41.418182 37.410922,41.372513 C 32.423455,41.280374 33.600393,36.266098 33.600393,36.266098 L 14.375479,36.328843 z "
+ id="path9222"
+ sodipodi:nodetypes="csccscc" />
+ <path
+ style="fill:url(#linearGradient5147);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
+ d="M 4.8886262,4.2739318 L 43.01746,4.2739318 C 45.042579,4.2739318 46.518438,5.7015863 46.518438,7.9000726 L 46.529388,34.103908 C 46.529388,35.795975 46.001041,36.319137 44.494732,36.319137 L 3.5320635,36.300202 C 2.3527922,36.271409 1.513468,35.805541 1.4976345,34.280899 L 1.5128113,7.7123281 C 1.5128113,5.9385022 3.0522187,4.2739318 4.8886262,4.2739318 z "
+ id="rect5040"
+ sodipodi:nodetypes="ccccccccc" />
+ <rect
+ style="fill:#555753;fill-opacity:1;fill-rule:evenodd;stroke:#2e3436;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="rect9208"
+ width="37.00032"
+ height="22.996691"
+ x="5.5059438"
+ y="8.2973185" />
+ <path
+ sodipodi:type="inkscape:offset"
+ inkscape:radius="-0.875"
+ inkscape:original="M 4.875 4.28125 C 3.0385925 4.28125 1.5 5.9449242 1.5 7.71875 L 1.5 34.28125 C 1.5158335 35.805892 2.3519787 36.283708 3.53125 36.3125 L 44.5 36.3125 C 46.006309 36.3125 46.53125 35.785816 46.53125 34.09375 L 46.53125 7.90625 C 46.53125 5.7077637 45.056369 4.2812498 43.03125 4.28125 L 4.875 4.28125 z "
+ xlink:href="#rect5040"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
+ id="path5145"
+ inkscape:href="#rect5040"
+ d="M 4.875,5.0625 C 3.5670573,5.0625 2.375,6.3571498 2.375,7.625 L 2.375,34.1875 C 2.3812657,34.79084 2.515801,34.970853 2.65625,35.09375 C 2.796699,35.216647 3.0858905,35.332113 3.5625,35.34375 L 44.5,35.34375 C 45.151173,35.34375 45.356981,35.24273 45.4375,35.15625 C 45.518019,35.06977 45.65625,34.755549 45.65625,34 L 45.65625,7.8125 C 45.65625,6.0053499 44.645463,5.0624999 43.03125,5.0625 L 4.875,5.0625 z " />
+ <path
+ style="opacity:0.6;fill:url(#linearGradient11406);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 13.992156,36.834708 C 14.075817,39.007257 13.708803,39.746151 12.358532,40.509939 L 35.973934,41.807257 C 35.024915,40.638629 33.644523,38.873923 34.020993,36.819022 L 13.992156,36.834708 z "
+ id="path10672"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#8d8d8f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:0.43902438"
+ d="M 9.9921262,42.291555 C 16.698819,42.293524 37.78891,42.291555 37.78891,42.291555"
+ id="path6575"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path8029"
+ d="M 9.647928,43.299429 C 16.354621,43.301398 38.367789,43.299429 38.367789,43.299429"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="opacity:0.4;fill:url(#linearGradient4778);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 6.031372,8.8268676 L 6.031372,26.834708 C 22.475817,25.480459 28.630065,16.722289 41.999999,15.807256 L 42,8.7954956 L 6.031372,8.8268676 z "
+ id="path4073"
+ sodipodi:nodetypes="ccccc" />
+ <g
+ id="g4754"
+ transform="translate(-46.77135,-7.3370294)">
+ <g
+ transform="matrix(0.916667,0,0,0.714282,49.771334,11.132681)"
+ style="opacity:0.3"
+ id="g22150">
+ <rect
+ style="opacity:1;fill:url(#radialGradient4770);fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:1.20000057;stroke-opacity:1"
+ id="rect22120"
+ width="4"
+ height="7"
+ x="0"
+ y="35" />
+ <rect
+ style="opacity:1;fill:url(#radialGradient4772);fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:1.20000057;stroke-opacity:1"
+ id="rect22134"
+ width="4"
+ height="7"
+ x="-48"
+ y="-42"
+ transform="scale(-1,-1)" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient4774);fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:1.20000057;stroke-opacity:1"
+ id="rect22138"
+ width="40"
+ height="7"
+ x="4"
+ y="35" />
+ </g>
+ <g
+ id="g4743">
+ <g
+ id="g10824"
+ transform="translate(-5.228666,-6.29601)">
+ <path
+ id="path4319"
+ d="M 57.5,14.928571 L 57.5,44.928571 L 96.5,44.928571 L 57.5,14.928571 z M 63.5,27.928571 L 78.5,38.928571 L 63.5,38.928571 L 63.5,27.928571 z "
+ style="fill:url(#linearGradient4776);fill-opacity:1;fill-rule:evenodd;stroke:#a38503;stroke-width:1.00000024px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path4326"
+ d="M 61.5,44.928571 L 61.5,41.928571"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path4328"
+ d="M 67.5,44.928571 L 67.5,41.928571"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path4330"
+ d="M 73.5,44.928571 L 73.5,41.964285"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path4332"
+ d="M 79.5,44.928571 L 79.5,41.928571"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path4334"
+ d="M 85.5,44.928571 L 85.5,41.928571"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#a38503;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ id="path6126"
+ d="M 58.500002,16.928575 L 58.500002,43.928586 L 93.500014,43.928586 L 58.500002,16.928575 z "
+ style="opacity:0.4;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.0000006px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <path
+ style="opacity:0.4;fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 9.5017474,38.542446 L 9.4575532,24.532894 L 28.541592,38.563262 L 9.5017474,38.542446 z "
+ id="path12221"
+ sodipodi:nodetypes="cccc"
+ transform="translate(47.771334,-4.867439)" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/panels/display/meson.build b/panels/display/meson.build
new file mode 100644
index 0000000..12f35cc
--- /dev/null
+++ b/panels/display/meson.build
@@ -0,0 +1,65 @@
+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(
+ desktop,
+ 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_desktop_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..b73f510
--- /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;
+}