Giter Site home page Giter Site logo

homebysix / docklib Goto Github PK

View Code? Open in Web Editor NEW
112.0 9.0 16.0 93 KB

Python module intended to assist IT administrators with manipulation of the macOS Dock.

License: Other

Python 95.35% Shell 4.65%
macadmins macadmin dock macos docklib dockutil defaults plistbuddy hacktoberfest python

docklib's Introduction

docklib

This is a Python module intended to assist IT administrators with manipulation of the macOS Dock.

Originally created as a Gist by @gregneagle, this fork has been modified to include support for some additional Dock features, and has been packaged for multiple distribution options.

docklib or dockutil?

The very capable dockutil tool serves a similar function to docklib. Why would Mac admins choose one over the other?

The primary benefit of docklib is that it allows the Dock to be manipulated in a "Pythonic" way. By parsing the Dock configuration into an object with attributes and data structures that can be modified using familiar functions like .append() and .insert(), docklib aims to make Python scripters feel at home.

In contrast, dockutil behaves more like a shell command-line utility and is written in Swift. This makes dockutil a good choice if you don't have a 'management python' or you're more comfortable writing user setup scripts in bash or zsh. Dockutil also has an --allhomes argument that allows Dock configuration for all users to be modified at the same time. Docklib isn't designed for this, instead focusing on configuring the Dock for the user that is currently logged in (for example, via an outset login-once or login-every script). Here's a great article to get you started with dockutil, if that sounds like what you're after.

Installation

There are multiple methods of installing docklib, depending on how you plan to use it.

Package installer

You can use the included build_pkg.sh script to build a macOS installer .pkg file. You can use this package to install docklib on your own Mac, or deploy the package using a tool like Jamf or Munki to install docklib on managed devices.

To run the script, cd to a local clone of this repository, then run:

./build_pkg.sh

The resulting pkg will be built in a temporary folder and shown in the Finder.

NOTE: The default install destination is /Library/Python/2.7/site-packages/docklib, which makes docklib available to the built-in macOS Python 2.7 framework. If you leverage a different Python installation, you'll need to modify this path in the build_pkg.sh script prior to building the installer package.

Pip

Docklib has been published to PyPI in order to make it available for installation using pip.

pip install docklib

This method is not intended to be used directly on managed devices, but it could be leveraged alongside a custom Python framework (like one built with macadmins/python or relocatable-python) using a requirements file.

Managed Python

Docklib is included in the "recommended" flavor of the macadmins/python release package. Installing this package and using #!/usr/local/managed_python3 for your docklib script shebang may be the most self-contained and future-proof way to deploy docklib.

Manual

Another method of using docklib is to simply place the docklib.py file in the same location as the Python script(s) you use to manipulate the macOS dock. Some examples of such scripts are included below.

Examples

Add Microsoft Word to the right side of the Dock

from docklib import Dock

dock = Dock()
item = dock.makeDockAppEntry("/Applications/Microsoft Word.app")
dock.items["persistent-apps"].append(item)
dock.save()

Add Microsoft Word to the left side of the Dock

from docklib import Dock

dock = Dock()
item = dock.makeDockAppEntry("/Applications/Microsoft Word.app")
dock.items["persistent-apps"] = [item] + dock.items["persistent-apps"]
dock.save()

Replace Mail.app with Outlook in the Dock

from docklib import Dock

dock = Dock()
dock.replaceDockEntry("/Applications/Microsoft Outlook.app", "Mail")
dock.save()

Remove Calendar from the Dock

from docklib import Dock

dock = Dock()
dock.removeDockEntry("Calendar")
dock.save()

Display the current orientation of the Dock

from docklib import Dock

dock = Dock()
print(dock.orientation)

Make the Dock display on the left, and enable autohide

from docklib import Dock

dock = Dock()
dock.orientation = "left"
dock.autohide = True
dock.save()

Add the Documents folder to the right side of the Dock

Displays as a stack to the right of the Dock divider, sorted by modification date, that expands into a fan when clicked. This example checks for the existence of the Documents item and only adds it if it's not already present.

import os
from docklib import Dock

dock = Dock()
if dock.findExistingEntry("Documents", section="persistent-others") == -1:
    item = dock.makeDockOtherEntry(
        os.path.expanduser("~/Documents"), arrangement=3, displayas=1, showas=1
    )
    dock.items["persistent-others"] = [item] + dock.items["persistent-others"]
    dock.save()

Add a URL to the right side of the Dock

Displays as a globe to the right of the Dock divider, that launches a URL in the default browser when clicked. This example checks for the existence of the Documents item and only adds it if it's not already present.

import os
from docklib import Dock

dock = Dock()
if dock.findExistingEntry("GitHub", section="persistent-others") == -1:
    item = dock.makeDockOtherURLEntry("https://www.github.com/", label="GitHub")
    dock.items["persistent-others"] = [item] + dock.items["persistent-others"]
    dock.save()

Specify a custom Dock for the local IT technician account

import os
from docklib import Dock

tech_dock = [
    "/Applications/Google Chrome.app",
    "/Applications/App Store.app",
    "/Applications/Managed Software Center.app",
    "/Applications/System Preferences.app",
    "/Applications/Utilities/Activity Monitor.app",
    "/Applications/Utilities/Console.app",
    "/Applications/Utilities/Disk Utility.app",
    "/Applications/Utilities/Migration Assistant.app",
    "/Applications/Utilities/Terminal.app",
]
dock = Dock()
dock.items["persistent-apps"] = []
for item in tech_dock:
    if os.path.exists(item):
        item = dock.makeDockAppEntry(item)
        dock.items["persistent-apps"].append(item)
dock.save()

Or if you prefer using a list comprehension:

import os
from docklib import Dock

tech_dock = [
    "/Applications/Google Chrome.app",
    "/Applications/App Store.app",
    "/Applications/Managed Software Center.app",
    "/Applications/System Preferences.app",
    "/Applications/Utilities/Activity Monitor.app",
    "/Applications/Utilities/Console.app",
    "/Applications/Utilities/Disk Utility.app",
    "/Applications/Utilities/Migration Assistant.app",
    "/Applications/Utilities/Terminal.app",
]
dock = Dock()
dock.items["persistent-apps"] = [
    dock.makeDockAppEntry(item) for item in tech_dock if os.path.exists(item)
]
dock.save()

More information

For more examples and tips for creating your docklib script, see my guides on:

docklib's People

Contributors

arubdesu avatar aysiu avatar discentem avatar homebysix avatar neilmartin83 avatar wardsparadox avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

docklib's Issues

Need help

Hello.

Can't use any of examples, for example, "Add App to the right side of the Dock" shows this:
Импортированный снимок экрана 2020-05-29 в 14 38 48

What I'm doing wrong?

Error loading "Foundation"?

I've tried installing "Foundation" separately but have no luck ...
I have tried docklib.py locally and through pip install.

Thoughts??

Traceback (most recent call last):
  File "/Users/griffin/Code/repos/temp/dockManager.py", line 1, in <module>
    from docklib import Dock
  File "/Users/griffin/Code/repos/temp/docklib.py", line 26, in <module>
    from Foundation import (
ModuleNotFoundError: No module named 'Foundation'

domain error when executed as root with sudo -u

when executed eg. via Munki (running as root)

sudo -u $loggedInUser python - <<EOF
from docklib import Dock
dock = Dock()
dock.orientation = 'left'
EOF

the command subprocess.call(['/bin/launchctl',.... fails with "Could not find domain for"

Issue with disabling show-recents

dock.show_recents = True; works as expected however when trying with False or 0, it does not disable the show recents.

I have tried the defaults write (full path to dock plist) show-recents -float 0
Then I did killall Dock command and it works which suggest the python script might be not passing the False (or 0) correctly.

On some com.apple.dock.plist files, there is no `len()` when looking at `self.items[section]`

Results in this kind of error when trying to remove items:

Traceback (most recent call last):
  File "NAMEOFSCRIPT.py", line 14, in <module>
    dock.removeDockEntry(item_to_remove)
  File "/Library/Python/2.7/site-packages/docklib.py", line 130, in removeDockEntry
    found_index = self.findExistingLabel(label, section=sect)
  File "/Library/Python/2.7/site-packages/docklib.py", line 117, in findExistingLabel
    for index in range(len(self.items[section])):
TypeError: object of type 'NoneType' has no len()

[Discussion] Should docklib be able to tell you whether a dock is default/uncustomized?

Proposal

Thus far in its history, docklib has objectively read/written dock configurations without imposing any sort of subjective judgment on them. However, it may be useful to make an exception to that rule in order to more easily determine the customization status of the current dock config. An is_default class attribute that returns True/False could provide this status.

Example

This boolean would be useful when writing docklib scripts that rely on idempotency to only take action under certain circumstances, like when the dock still contains Apple's default items.

If implemented, the boolean could be used like this:

from docklib import Dock
dock = Dock()
if dock.is_default:
    # customize the dock
else:
    print('Dock has already been modified from Apple default')

Questions

I have two questions to the community of docklib scripters:

What criteria should be used?

In my exploration, I've collected a few loose criteria for determining whether a dock is default:

  • Contents of persistent-apps and persistent-others: This is the criteria I've found most palatable. I have collected a list of apps listed in Apple's default dock from the last few major versions of macOS. If the dock only contains items in this list, there's a high probability that the dock is default. However, this doesn't take into account two categories of users:

    • Users who genuinely prefer first-party apps and modify their dock very lightly from Apple's configuration, perhaps just removing 2-3 apps they don't use and keeping the rest as is.
    • Users who remove ALL items from the dock, possibly because they intend to hide the dock and use a different solution (e.g. uBar), or perhaps because they don't use the dock at all.
  • mod-count: In an ideal world, uncustomized docks would have a mod-count of zero and our job would be done. However, user modification actions don't appear to be the sole trigger for mod-count to increment: Apple's dock "fixups," which add newly featured apps to the dock after OS upgrades, can increment the count. Automated processes can increment the count, even if they don't actually modify the dock contents. And user modifications that have no meaningful effect (like changing the position of a default icon) also increment the count, even though such modifications should likely not prevent the dock from being considered "default." Therefore, in my implementations I have ignored mod-count.

  • Length of dock: Not to be considered in isolation, but rather combined with one or both of the criteria above — the minimum and maximum length of a default Apple dock should be knowable and used to eliminate false positives.

Is this useful?

Big picture: There's something to be said for keeping docklib as simple as possible. One good example of this: I've decided not to have docklib offer any feature that backs up the dock plist prior to making changes. This is something administrators can handle their own way.

In my view, whether is_default should be left to each admin to implement or provided by docklib itself depends upon (a) how many people would find it genuinely useful, and (b) how many people would contribute to maintaining the feature as Apple's default dock changes in the future.

If we decide docklib should not include is_default, then I'd probably put together a reference script that administrators can use to get a head start on adding their own version of the feature.

I'll stop here and listen to some community feedback. What do you think?

Recently used not updated

Using docklib 1.0.3 branch with show_recents fix. MacOS 10.14.5

When adding items to the dock, if the item already exists in recently used, it will remain in recently used (therefore appearing twice) after calling dock.save().

Expected result is the recently used would be updated/refreshed with dock.save()

Workaround: from terminal calling killall 'Dock' to update/refresh the recently used

docklib errors out when run on 10.13.16

/System/Library/LaunchAgents/com.apple.Dock.plist: Could not find specified service
Traceback (most recent call last):
  File "/usr/local/outset/login-once/DefaultDock.py", line 26, in <module>
    dock.save()
  File "/Library/Python/2.7/site-packages/docklib.py", line 61, in save
    if getattr(self, key):
AttributeError: Dock instance has no attribute 'show-recents'

Thanks to @WardsParadox for finding the issue, which is show-recents not being available until 10.14.

DockLib < 10.14 using show_recents errors out with AttributeError

Docklib when used to set attributes for preferences not available to the OS errors out with AttributeError. If you are setting this attribute, make sure you have a test for the OS it exists on.
Example:

import docklib
import platform
from distutils.version import LooseVersion

dock = docklib.Dock()

if LooseVersion(platform.mac_ver()[0]) >= LooseVersion("10.14"):
    dock.show_recents = True;
dock.
dock.save()

Python3 Compatibility

When attempting to use docklib in a python3 environment, we get this error. I haven't looked into it fully yet, just logging it now.

 import docklib
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Python/2.7/site-packages/docklib.py", line 28, in <module>
    class Dock:
  File "/Library/Python/2.7/site-packages/docklib.py", line 68, in Dock
    "dblclickbehavior", "show-recents-immutable", "windowtabbing"
TypeError: append() takes exactly one argument (3 given)

Docklib is too dependent upon labels, which makes it difficult to write language-independent dock scripts

Problem

I'm using docklib to configure the dock upon login after end users walk through the macOS Setup Assistant. In order to make my script idempotent, I'm searching for existing dock items in order to dynamically determine whether the dock requires customization. Some of the items I'm searching for are Apple default apps: Notes, Messages, Music, Reminders, etc.

This works fine as long as the user has selected English as their preferred language. However, when another language is selected, the Dock item labels are localized in that language — e.g. Notas, Mensajes, Música, Recordatorios — and my script fails.

image

My workaround has been to use the _CFURLString of the dock items for the basis of comparison, since the apps' path on disk is not affected by localization. However, docklib doesn't make this easy for me, since its current functions are focused on finding, removing, and replacing items based on file-label.

Proposed Solution

I would propose making a new findExistingEntry function that allows finding items by either label (default), path, or basename:

    def findExistingEntry(self, search_str, by="label", section="persistent-apps"):
        section_items = self.items[section]
        if section_items:
            for index, item in enumerate(section_items):
                urlstring = unquote(
                    item["tile-data"].get("file-data", {}).get("_CFURLString", "")
                )
                if by == "label":
                    # Most dock items use "file-label", but URLs use "label"
                    for label_key in ("file-label", "label"):
                        if item["tile-data"].get(label_key) == search_str:
                            return index
                elif by == "path" and urlstring:
                    if urlparse(urlstring.rstrip("/")).path == search_str:
                        return index
                elif by == "basename" and urlstring:
                    if (
                        os.path.basename(urlparse(urlstring.rstrip("/")).path)
                        == search_str
                    ):
                        return index

        return -1

And modifying the findExistingLabel function to serve as a pointer to the new function:

    def findExistingLabel(self, test_label, section="persistent-apps"):
        return self.findExistingEntry(test_label, by="label", section=section)

I’m still thinking about how removeDockEntry, replaceDockEntry, and other label-centric functions should be adapted. Open to feedback if anybody has strong opinions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.