0

Beliebige Programme per C# manipulieren

Programme per C# manipulieren - am Beispiel NotepadsIn folgendem Artikel soll es darum gehen, wie man mittels C# andere Programme ansteuern, anpassen und erweitern kann. Als Beispielszenario wollen wir mittels einer kleinen C#-Applikation das standardmäßig mit Windows ausgelieferte Programm “Notepad” um eine Zusatzfunktion erweitern.

Konkret soll sich die C#-Anwendung in alle geöffneten Notepad Instanzen einklinken, die Oberfläche um einen neuen Button erweitern und Klicks auf diesen Button abfangen. Bei einem Klick auf den Button soll der Text aus dem Notepad-Textfeld an unsere Anwendung geschickt und mittels einem Markdown Parser (Was ist Markdown?) in HTML umgewandelt werden.

Das fertige Endprodukt unseres Artikels soll dann wie in dem nachfolgenden Video aussehen, dass ich für euch erstellt habe.

Im Rahmen des Artikel werden wir das Programm Schritt-für-Schritt aufbauen. Wer gleich den kompletten Quelltext haben will, scrollt direkt bis ans Ende des Artikels. Dort ist der komplette C#-Code dargestellt. Alternativ steht auch ein Download der Visual Studio Solution bereit.

Andere Programme per C# manipulieren

Zu Beginn legen wir eine neues WinForms-Projekt in Visual Studio an. Im GUI-Designer fügen wir dem Standard-Form eine RichTextBox hinzu, benennen diese als “richTextBoxInfo” und setzen das Dock-Attribut auf “Fill”. Nun sollte das Formular wie in nebenstehendem Screenshot aussehen.

Im nächsten Schritt fügen wir im Projektmappen-Explorer unter Verweise mittels Rechtsklick zwei neue Verweise hinzu. Einen Verweis auf die WindowsBase.dll, welche über den “.NET”-Reiter aufzufinden ist und einen Verweis auf “MarkdownSharp.dll” über den “Durchsuchen”-Reiter. Die MarkdownSharp.dll kann von der MarkdownSharp-Projektseite heruntergeladen werden. Ist dies geschehen, können wir mit dem Ausprogrammieren des Programms beginnen.

Im ersten Schritt fügen wir drei neue using-Referenzen hinzu: System.Runtime.InteropServices, System.Diagnostics und MarkdownSharp. InteropService benötigen wir um Methoden aus nicht-gemanagetem (unmanaged) Code aufzurufen, System.Diagnostics brauchen wir um Prozesse auflisten und auswerten zu können und Markdown benötigen wir, um später den Markdown-Text parsen zu können.

Im zweiten Schritt fügen wir eine ganze Latte an “unmanaged Code”-Methoden, Konstanten und Hilfsvariablen hinzu. Welcher Part wozu dient, wird sich im Laufe des Artikels klären. Deshalb können wir diese vorerst getrost aus nachfolgender Code-Box kopieren.

Kleiner Tipp am Rande: Wer sich fragt, woher man weiß, wie so eine native/unmanaged Methodensignatur aussehen muss, der kann mal einen Blick auf pinvoke.net werfen.

Unser Code sollte nun wie folgt aussehen:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using MarkdownSharp;

namespace NotepadEnhanced
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll")]
        private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count); 

        [DllImport("user32.dll")]
        private static extern IntPtr GetMenu(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool InsertMenu(IntPtr hMenu, Int32 wPosition, Int32 wFlags, Int32 wIDNewItem, string lpNewItem);

        [DllImport("user32.dll")]
        private static extern int GetMenuItemCount(IntPtr hMenu);

        [DllImport("user32.dll")]
        private static extern bool DrawMenuBar(IntPtr hWnd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindowEx(IntPtr hwndParent,
            IntPtr hwndChildAfter, string lpszClass, IntPtr lpszWindow);

        [DllImport("user32.dll", EntryPoint = "SendMessage")]
        private static extern IntPtr SendMessageGetTextW(IntPtr hWnd,
            uint msg, UIntPtr wParam, StringBuilder lParam);

        [DllImport("User32.dll", EntryPoint = "SendMessage")]
        private extern static int SendMessageGetTextLength(IntPtr hWnd,
            int msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
           hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
           uint idThread, uint dwFlags);

        [DllImport("user32.dll")]
        static extern bool UnhookWinEvent(IntPtr hWinEventHook);

        delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
                      IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

        WinEventDelegate procDelegate;
        IntPtr hhook;

        const uint EVENT_OBJECT_INVOKED = 0x8013;
        const uint WINEVENT_OUTOFCONTEXT = 0;
        private const Int32 _CustomMenuID = 1000;
        private const Int32 MF_BYPOSITION = 0x400;
        private const Int32 WM_SYSCOMMAND = 0x112;

        List<IntPtr> handlesNp = new List<IntPtr>();
        Dictionary<IntPtr, IntPtr> handlesNpEdit = new Dictionary<IntPtr, IntPtr>();

        public Form1()
        {
            InitializeComponent();
        }
    }
}

Prozesse identifizieren und Handles auslesen

Nun legen wir einen Eventhandler für das Load-Event unseres Hauptformulars (Form1) an. Ob ihr das von Hand oder über den Visual Studio Designer macht, spielt dabei keine Rolle.

Innerhalb der Load-Methode rufen wir beim System alle Notepad-Prozesse ab und speichern diese in einer Liste. Schließlich müssen wir die Programmfenster ja kennen, wenn wir sie erweitern wollen.

Danach durchlaufen wir die Liste mit allen Notepad-Fenster-Instanzen. Hierbei fragen wir zuerst den Text der im Fenster-Titel steht ab und geben diesen in der richTextBoxInfo auf unserem Hauptformular aus.

Danach erfragen wir einen Verweis auf das Textfeld des jeweiligen Notepad-Fensters. Diesen brauchen wir, um später Zugriff auf den Text aus dem Notepad-Fenster zu bekommen.

Abschließend überprüfen wir, wie viel Buttons das Notepad-Fenster in der Menüleiste hat. Wenn es weniger als 6 Buttons hat, dann wurde es noch nicht manipuliert. In diesem Fall fügen wir unseren Button hinzu.

Ist die Liste mit allen Notepad-Fenstern durchlaufen, setzen wir noch einen EventHook. Hierdurch klinken wir uns in System ein und können Windows-Nachrichten abfangen.

Der Code in der Load-Methode sieht wie folgt aus. (Anhand der Kommentare könnt ihr auch noch einmal nachvollziehen, was in den einzelnen Schritten passiert.)

private void Form1_Load(object sender, EventArgs e)
{
    //Die Fenster-Handles aller Notepad-Prozesse abfragen und speichern
    handlesNp = Process.GetProcessesByName("notepad").Select(x => x.MainWindowHandle).ToList();

    //Durchlaufe alle gefundenen Notepad-Instanzen
    richTextBoxInfo.Text = "An folgende Fenster angedockt:\r\n";
    foreach (var npHandle in handlesNp.Select((x, i) => new {Handle = x, Index = i}))
    {
        //Gebe den Namen der aktuellen Notepad-Instanz aus
        StringBuilder sb = new StringBuilder();
        GetWindowText(npHandle.Handle, sb, 200);
        var windowTitle = sb.ToString();
        richTextBoxInfo.Text += windowTitle + "\r\n";

        //Ermittle das Handles des Textfeldes der aktuellen Notepad-Instanz
        //und speichere es, um später den Text auszulesen
        handlesNpEdit.Add(npHandle.Handle, FindWindowEx(npHandle.Handle, IntPtr.Zero, "Edit", IntPtr.Zero));

        //Ermittle Handles des Menüs der aktuellen Notepad-Instanz
        var sysMenu = GetMenu(npHandle.Handle);
        //Wenn das Menü noch keinen Zusatzbutton bekommen hat,
        //füge den neuen Button hinzu und zeichne Menü neu
        if (GetMenuItemCount(sysMenu) != 6)
        {
            InsertMenu(sysMenu, 4, MF_BYPOSITION, _CustomMenuID, "MD zu HTML");
            DrawMenuBar(npHandle.Handle);
        }
    }

    //Hook setzen, um Windows-Nachrichten abzufangen
    procDelegate = new WinEventDelegate(WinEventProc);
    hhook = SetWinEventHook(EVENT_OBJECT_INVOKED, EVENT_OBJECT_INVOKED, IntPtr.Zero,
            procDelegate, 0, 0, WINEVENT_OUTOFCONTEXT);
}

Wenn wir nun versuchen das Programm zu kompilieren, dann dürfte uns Visual Studio eine nette Fehlermeldung um die Ohren werfen. Dies liegt an der folgenden Zeile Code, die wir am Ende der Load-Methode geschrieben haben.

procDelegate = new WinEventDelegate(WinEventProc);

Hier übergeben wir als Parameter eine Methode namens “WinEventProc”, die in unserem Programm jedoch noch gar nicht existiert. Deshalb legen wir nun im nächsten Schritt eben diese Methode an und befüllen sie mit “Leben”.

Windows-Nachrichten mit C# abfangen

Da diese Methode eine ganze Menge an Windows-Nachrichten abfängt, wir jedoch nur auf solche eingehen wollen, die durch den Klick auf unseren neu hinzugefügten Button in Notepad ausgelöst wurden, filtern wir zuerst mittels eine if-Abfrage nur die relevanten Nachrichten heraus. Hierzu überprüfen wir erstens, ob die Nachricht von einer der Notepad-Instanzen kommt, die wir in der Load-Methode in unsere Liste geschrieben haben (handlesNp.Contains(hwnd)) und zweitens, ob die Nachricht von unserem Button ausgelöst wurde (_CustomMenuID == idChild).

Wenn dem so ist, holen wir mittels der GetWindowText()-Methode den Text aus dem Notepad-Textfeld. Danach erstellen wir eine Markdown-Instanz und wandeln den Notepad-Text von Markdown in HTML.

Nun erstellen wir ein neues Fenster/Formular, platzieren einen Webbrowser sowie eine Richtextbox darauf und übergeben beiden unser soeben frisch geparstes HTML.

Abschließend öffnen wir das neue Fenster und holen es in den Vordergrund. Der Code für all das sieht wie folgt aus:

void WinEventProc(IntPtr hWinEventHook, uint eventType,
       IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
    //Überprüfe ob Nachricht von Notepad kommt und durch unseren
    //eigenen Button ausgelöst wurde.
    if (handlesNp.Contains(hwnd) && _CustomMenuID == idChild)
    {
        //Hole Text aus Notepad-Textfeld
        var markdownText = GetWindowText(handlesNpEdit[hwnd]);

        //Rende Text von Markdown nach HTML
        Markdown mds = new Markdown();
        var htmlOutput = htmlBody.Replace("{html_body}", mds.Transform(markdownText));

        //Erstelle neues Fenster/Formular
        Form fWeb = new Form();
        fWeb.Size = new System.Drawing.Size(800, 600);
        fWeb.StartPosition = FormStartPosition.CenterScreen;
        fWeb.Text = "Notepad - Markdown Extension";

        //Füge dem Fenster einen Webbrowser hinzu und lade
        //HTML in den Browser
        WebBrowser wb = new WebBrowser();
        wb.Dock = DockStyle.Fill;
        wb.ScriptErrorsSuppressed = true;
        wb.DocumentText = htmlOutput;

        //Füge dem Fenster eine Textbox hinzu und lade
        //HTML in die Textbox
        RichTextBox rtHtml = new RichTextBox();
        rtHtml.Dock = DockStyle.Fill;
        rtHtml.Text = htmlOutput;

        //Order Browser und Textbox 50/50 im Fenster an
        TableLayoutPanel tlp = new TableLayoutPanel();
        tlp.Dock = DockStyle.Fill;
        tlp.ColumnCount = 1;
        tlp.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
        tlp.RowCount = 2;
        tlp.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
        tlp.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
        tlp.Controls.AddRange(new Control[] { wb, rtHtml });
        fWeb.Controls.Add(tlp);

        //Fenster anzeigen
        fWeb.Show();
        SetForegroundWindow(fWeb.Handle);
    }

}

Ein Klick auf F5, der Kompiler läuft and und … Fehler! Diesmal haben wir zwar die Hook-Methode, dafür haben wir soeben eine neue Methode verwendet, die noch nicht existiert. Deshalb fügen wir nun noch die fehlende GetWindowText()-Implementierung hinzu.

public static string GetWindowText(IntPtr hwnd)
{
   int len = SendMessageGetTextLength(hwnd, 14, IntPtr.Zero, IntPtr.Zero) + 1;
   StringBuilder sb = new StringBuilder(len);
   SendMessageGetTextW(hwnd, 13, new UIntPtr((uint)len), sb);
   return sb.ToString();
}

Eine Variable fehlt auch noch. Und zwar der htmlBody-String. Dieser enthält HTML- und CSS-Code zur Darstellung unseres Textes in dem Webbrowser-Element. (Da der Code etwas länger ist, habe ich unten stehenden Codeblock zusammengeklappt. Ein Klick auf die Code-Box hilft weiter.)

#region Markdown / CSS-Code
string htmlBody = @"
  <!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Transitional//EN"" ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"">
  <html xmlns=""http://www.w3.org/1999/xhtml"" xml:lang=""en"" lang=""en"">
  <head>
      <style type=""text/css"">;
          body {
          font-family: Helvetica, arial, sans-serif;
          font-size: 14px;
          line-height: 1.6;
          padding-top: 10px;
          padding-bottom: 10px;
          background-color: white;
          padding: 30px; }

        body > *:first-child {
          margin-top: 0 !important; }
        body > *:last-child {
          margin-bottom: 0 !important; }

        a {
          color: #4183C4; }
        a.absent {
          color: #cc0000; }
        a.anchor {
          display: block;
          padding-left: 30px;
          margin-left: -30px;
          cursor: pointer;
          position: absolute;
          top: 0;
          left: 0;
          bottom: 0; }

        h1, h2, h3, h4, h5, h6 {
          margin: 20px 0 10px;
          padding: 0;
          font-weight: bold;
          -webkit-font-smoothing: antialiased;
          cursor: text;
          position: relative; }

        h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
          background: url('../../images/modules/styleguide/para.pn') no-repeat 10px center;
          text-decoration: none; }

        h1 tt, h1 code {
          font-size: inherit; }

        h2 tt, h2 code {
          font-size: inherit; }

        h3 tt, h3 code {
          font-size: inherit; }

        h4 tt, h4 code {
          font-size: inherit; }

        h5 tt, h5 code {
          font-size: inherit; }

        h6 tt, h6 code {
          font-size: inherit; }

        h1 {
          font-size: 28px;
          color: black; }

        h2 {
          font-size: 24px;
          border-bottom: 1px solid #cccccc;
          color: black; }

        h3 {
          font-size: 18px; }

        h4 {
          font-size: 16px; }

        h5 {
          font-size: 14px; }

        h6 {
          color: #777777;
          font-size: 14px; }

        p, blockquote, ul, ol, dl, li, table, pre {
          margin: 15px 0; }

        hr {
          background: transparent url('../../images/modules/pulls/dirty-shade.png') repeat-x 0 0;
          border: 0 none;
          color: #cccccc;
          height: 4px;
          padding: 0; }

        body > h2:first-child {
          margin-top: 0;
          padding-top: 0; }
        body > h1:first-child {
          margin-top: 0;
          padding-top: 0; }
          body > h1:first-child + h2 {
            margin-top: 0;
            padding-top: 0; }
        body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
          margin-top: 0;
          padding-top: 0; }

        a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
          margin-top: 0;
          padding-top: 0; }

        h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
          margin-top: 0; }

        li p.first {
          display: inline-block; }

        ul, ol {
          padding-left: 30px; }

        ul :first-child, ol :first-child {
          margin-top: 0; }

        ul :last-child, ol :last-child {
          margin-bottom: 0; }

        dl {
          padding: 0; }
          dl dt {
            font-size: 14px;
            font-weight: bold;
            font-style: italic;
            padding: 0;
            margin: 15px 0 5px; }
            dl dt:first-child {
              padding: 0; }
            dl dt > :first-child {
              margin-top: 0; }
            dl dt > :last-child {
              margin-bottom: 0; }
          dl dd {
            margin: 0 0 15px;
            padding: 0 15px; }
            dl dd > :first-child {
              margin-top: 0; }
            dl dd > :last-child {
              margin-bottom: 0; }

        blockquote {
          border-left: 4px solid #dddddd;
          padding: 0 15px;
          color: #777777; }
          blockquote > :first-child {
            margin-top: 0; }
          blockquote > :last-child {
            margin-bottom: 0; }

        table {
          padding: 0; }
          table tr {
            border-top: 1px solid #cccccc;
            background-color: white;
            margin: 0;
            padding: 0; }
            table tr:nth-child(2n) {
              background-color: #f8f8f8; }
            table tr th {
              font-weight: bold;
              border: 1px solid #cccccc;
              text-align: left;
              margin: 0;
              padding: 6px 13px; }
            table tr td {
              border: 1px solid #cccccc;
              text-align: left;
              margin: 0;
              padding: 6px 13px; }
            table tr th :first-child, table tr td :first-child {
              margin-top: 0; }
            table tr th :last-child, table tr td :last-child {
              margin-bottom: 0; }

        img {
          max-width: 100%; }

        span.frame {
          display: block;
          overflow: hidden; }
          span.frame > span {
            border: 1px solid #dddddd;
            display: block;
            float: left;
            overflow: hidden;
            margin: 13px 0 0;
            padding: 7px;
            width: auto; }
          span.frame span img {
            display: block;
            float: left; }
          span.frame span span {
            clear: both;
            color: #333333;
            display: block;
            padding: 5px 0 0; }
        span.align-center {
          display: block;
          overflow: hidden;
          clear: both; }
          span.align-center > span {
            display: block;
            overflow: hidden;
            margin: 13px auto 0;
            text-align: center; }
          span.align-center span img {
            margin: 0 auto;
            text-align: center; }
        span.align-right {
          display: block;
          overflow: hidden;
          clear: both; }
          span.align-right > span {
            display: block;
            overflow: hidden;
            margin: 13px 0 0;
            text-align: right; }
          span.align-right span img {
            margin: 0;
            text-align: right; }
        span.float-left {
          display: block;
          margin-right: 13px;
          overflow: hidden;
          float: left; }
          span.float-left span {
            margin: 13px 0 0; }
        span.float-right {
          display: block;
          margin-left: 13px;
          overflow: hidden;
          float: right; }
          span.float-right > span {
            display: block;
            overflow: hidden;
            margin: 13px auto 0;
            text-align: right; }

        code, tt {
          margin: 0 2px;
          padding: 0 5px;
          white-space: nowrap;
          border: 1px solid #eaeaea;
          background-color: #f8f8f8;
          border-radius: 3px; }

        pre code {
          margin: 0;
          padding: 0;
          white-space: pre;
          border: none;
          background: transparent; }

        .highlight pre {
          background-color: #f8f8f8;
          border: 1px solid #cccccc;
          font-size: 13px;
          line-height: 19px;
          overflow: auto;
          padding: 6px 10px;
          border-radius: 3px; }

        pre {
          background-color: #f8f8f8;
          border: 1px solid #cccccc;
          font-size: 13px;
          line-height: 19px;
          overflow: auto;
          padding: 6px 10px;
          border-radius: 3px; }
          pre code, pre tt {
            background-color: transparent;
            border: none; }
    </style>
<title>$_</title>
<meta http-equiv=""Content-Type"" content=""text/html; charset=utf-8"" />
</head>
<body>
{html_body}
</body>
</html>";
#endregion

Jetzt sind wir fast fertig. Im letzten Schritt fügen wir unserem Formular noch den “Form-Closing”-Eventhandler hinzu. Innerhalb dieses Eventhandlers stellen wir sicher, dass der von uns gesetzte Hook zum Abfangen der Windows-Nachrichten beim Schließen des Programms wieder entfernt wird.

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
	//Nachrichten-Hook entfernen
	UnhookWinEvent(hhook);
}

Nun ist alles komplett. Zum Testen öffnen wir Notepad und schreiben ein paar Zeilen Text in Markdown-Formatierung. Nun kompilieren und starten wir unser Programm. Sobald das Fenster erscheint, sollte in Notepad ein neuer Button in der Menüleiste erscheinen.

Funktionsweise - Notepad mit C# um Markdown erweitern

Ein Klick auf den Button sollte ein weiteres Fenster öffnen, in dem unser Markdown-Text einmal gerendert und einmal als reines HTML dargestellt wird.

Kompletter Sourcecode und Download

Wem das Ganze zu verwirrend war oder wer keine Zeit hat, der findet nachfolgend noch einmal den gesamten Quellcode als komplettes Visual Studio Projekt zum Download.

Download: Visual Studio Beispielprojekt

 

 

Fragen, Ideen & Kritik

Damit sind wir für heute auch schon wieder durch. Solltet ihr noch Fragen, Anregungen oder Kritik haben, dann schreib mit einfach einen Kommentar.

Wenn ihr Wünsche für weitere Themen bzw. Artikel habt, dann lasst mir diese einfach zukommen.

0

Mac OS X Spotlight Index neu erstellen

Mac OSX Spotlight Index neu erstellenSeit Kurzem bin auch ich stolzer Besitzer eines Apple Produkts. Mit dem 13″ Macbook Air hat nach über 10 Jahren Windows-Monarchie das erste Apple-Gerät Einzug in meine IT-Landschaft gehalten, sodass es von nun an neben Windows- und vereinzelten Linux-Artikeln auch immer wieder Artikel zum Thema Apple/MAC OS X geben wird. Doch kommen wir zum eigentlichen Thema zurück.

Die Universalsuche Spotlight in Mac OSX ist ohne Frage ein sehr mächtiges Tool. Wenn Spotlight jedoch streikt und nicht mehr alle Apps oder Dateien findet, dann verliert das Werkzeug seinen Sinn. In solchen Fällen kann es helfen den Spotlight Index neu zu erstellen oder auch nur eine einzelne App wieder in den Index aufzunehmen.

Problem Eingrenzung

Wenn Spotlight nicht mehr die gewünschten Suchergebnisse liefert, obwohl die gesuchte App auf jeden Fall im System vorhanden ist, dann gibt es mindestens zwei Möglichkeiten das Problem zu lösen.

Welcher Lösungsansatz der passende ist, hängt von der Problemstellung ab. Wenn nur eine einzelne App fehlt, dann kann dies durch falsch gesetzte Attribute hervorgerufen werden. Fehlen hingegen mehrere Apps oder Dateien, dann hilft, es den Index von Spotlight zu löschen und anschließend neu aufzubauen.

Einzelne App zu Spotlight hinzufügen

Fehlt nur eine einzelne App, kann dies an fehlerhaften Attributen liegen. Dies lässt sich mittels des mdls-Befehls im Terminal überprüfen. Hierzu genügt ein Aufruf in Form von mdls /Applications/NameDerApp.app | grep MDSystemFile. Für Safari sähe der Aufruf dann wie folgt aus:

mdls /Applications/Safari.app | grep MDSystemFile

Wenn dieser Aufruf im Terminal nun eine Ausgabe erzeugt (eine Zeile gefunden hat), dann ist die App von der Problematik betroffen. Dieser Fehler lässt sich nun mit folgendem Befehl beheben.

sudo xattr -w com.apple.metadata:kMDItemSupportFileType "" /Applications/Safari.app

In oben stehendem Befehl müsst ihr natürlich das Safari durch den Namen/Pfad eurer App ersetzen.

Sollte eure App nicht von der Attribut-Problematik betroffen sein, bleibt noch der zweite Lösungsansatz – die Neuerstellung des Spotlight-Suchindex.

Spotlight Suchindex neu erstellen

Wenn der erste Lösungsansatz nicht zum Erfolg geführt hat bzw. die Symptome nicht gegeben waren, kann es helfen den kompletten (Such-)Index von Spotlight neu erstellen zu lassen. Hierzu sind vier Befehle nötig, die wieder im Terminal eingegeben werden müssen.

Der erste Befehl stoppt Spotlight:

sudo mdutil -a -i off

Der zweite Befehl deaktiviert den Spotlight-Service samt Index:

sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist

Der dritte Befehl lädt den Spotlight-Server wieder ins System:

sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist

Der vierte Befehl startet Spotlight wieder:

sudo mdutil -a -i on

Nachdem vierten Befehl sollte Spotlight starten und euch darüber informieren, dass der Index neu angelegt wird. Dies kann, je nach Auslastung des Systems und Anzahl der Apps und Dateien ein paar Minuten dauern.

Ist die Reindexierung abgeschlossen, sollten alle Apps wieder gefunden werden.

Weitere Tipps und Lösungsansätze

Sollten euch die beiden Lösungsansätze geholfen haben, freue ich mich über jegliches Feedback. Sollten die Vorschläge nicht zum Erfolg geführt haben, ihr aber einen anderen Lösungsweg entdeckt haben, dann schreibt mir doch einfach einen Kommentar.
Wenn nichts dagegen spricht, werde ich den Artikel dann gerne erweitern und euch natürlich auch namentlich nennen.

2

Unbekannte Anrufer sofort entlarven

CIA Anruferkennung - Unbekannte Anrufer entlarvenIch denke jeder kennt folgende Situation: Das Smartphone klingelt, doch der Anrufer ist unbekannt. Soll man nun dran gehen oder nicht?

Meistens entscheide ich mich fürs Drangehen, wobei ich dennoch entspannter bin, wenn ich vor dem Annehmen des Anrufs schon weiß, mit wem ich gleich sprechen werde – selbst wenn es nur die 2-3 Sekunden sind, in denen ich den Anrufer auf meinem Display sehe, bevor ich den Anruf annehme.

Doch kommen wir zurück zur eigentlichen Problematik – den unbekannten Anrufern. Mittels einer kleinen Android-App lässt sich dieses Problem nämlich in vielen Fällen eliminieren, sodass ein unbekannter Anrufer nur noch in den seltensten Fällen wirklich unbekannt bleiben dürfte.

Unbekannte Anrufer per App erkennen

Um dem Problem aus dem Weg zu gehen, gibt es im Google Play Store gleich eine ganze Handvoll Apps, die versprechen, unbekannte Anrufer zu ermitteln. Ich habe ein paar davon getestet und konnte mit der App “CIA Anruferkennung” die besten Resultate erzielen.

CIA Anruferkennung in AktionDie App ist kostenlos im Play Store erhältlich und die Installation sowie Einrichtung gehen relativ schmerzfrei über die Bühne. Direkt beim ersten Start möchte die App sich in diversen Social Networks einloggen. Dies kann man machen, muss man aber nicht. Ich persönlich halte meine Daten lieber bei mir und habe alle Anmeldungen übersprungen.

Geht nun ein Anruf ein, so probiert CIA Anruferkennung im Hintergrund den Anrufer hinter der Nummer zu ermitteln und gibt diesen in einem kleinen Nachrichtenfeld am oberen Rand des Smartphone-Displays aus.

Dies klappt jedoch nicht immer perfekt, was mehrere Gründe hat. Zum einen benötigt die App für die Abfrage des Nutzers Internet. Im WLAN geht dies in den meisten Fällen noch recht fix, im mobilen Netz kann es hingegen schon etwas länger dauern. Wenn dann nur noch GPRS oder EDGE zur Verfügung steht, klappt es gar nicht mehr, da bei diesen beiden Netztechnologien entweder nur Telefonie oder Internet funktioniert. Eine Internetabfrage des Anrufers während eines eingehenden Anrufs kann also nicht klappen.

Das zweite Problem sind die Quellen, die CIA Anruferkennung anzapft. Leider sind eben diese nicht ersichtlich. In meinen Tests konnte ich jedoch feststellen, dass die App das größte Verzeichnis für den deutschsprachigen Raum (DasTelefonbuch), zum Beispiel nicht nutzt.

Anders kann ich es mir nicht erklären, dass unter der Telefonnummer meiner Mutter mein Name ermittelt wird. CIA gibt hier als Quelle schlicht und einfach “Web” an. Jedoch wohne ich schon seit Jahren nicht mehr in meinem Elternhaus und so hätte eine Abfrage aus dem Telefonbuch ergeben, dass unter der Nummer meine Mutter zu erreichen ist.

In solchen Fällen hätte ein Blick ins mobile Telefonbuch wohl mehr geholfen, als die Nutzung der App.

Fazit – Brauchbar oder nicht?

Kommen wir zum Fazit. In den letzten Absätzen ist die App nicht so gut weggekommen. Dennoch finde ich die App relativ praktisch und werde sie in den nächsten Wochen weiterhin ausgiebig testen. Für die Problematik mit dem Internet können die App-Entwickler nichts. Das ist einfach eine technische Gegebenheit, über die ein Entwickler sich nicht hinwegsetzen kann.

Für die Wahl der Quellen können die Entwickler hingegen schon etwas. Hier wird der Langzeittest zeigen, wie brauchbar die Informationen sind, die die App liefert und wie oft sie mich fehlleiten wird.

Abschließend würde ich gerne von euch wissen, was ihr von der App haltet. Ist sie brauchbar oder nicht? Würdet ihr solch eine App einsetzen oder habt ihr Bedenken, dass eine App die Informationen, wann ihr von wem angerufen wurdet, ins Internet übermittelt?

2

Mein Hoster des Vertrauens

Netcup - der Webhoster für NerdsDa ich schon ein paar Mal gefragt wurde, wo ich diesen Blog hier hoste und wie zufrieden ich bin, soll es heute um meinen Hoster des Vertrauens gehen.

Wenn jemand seine Sache gut macht, dann kann man das auch mal loben. Und genau darum geht es hier. Der Artikel ist in keinster Weise durch meinen Hoster gesponsort, sondern ein Erfahrungsbericht meinerseits.

Doch bevor es losgeht, noch ein kurzer Rückblick, wo ich meinen Blog bisher gehostet habe.

Per Anhalter durch das Internet

Meine Reise durch die (Web-)Hoster-Welt fing relativ schmerzhaft an. Als ich vor gut 6 Jahren meinen ersten Blog, den Vorgänger des heutigen code-bude.net, startete, wollte ich gleich selber hosten und suchte deshalb nach einem passenden Hoster. Gelandet bin ich bei servcity.org. Ständige Downtimes waren an der Tagesordnung. Konsequenz ich kündigte. Etwas später flatterten dann die ersten Abmahnungen ins Haus. ServCity hatte im Hintergrund den Inhaber gewechselt und dieser behauptete, meine Kündigung nirgends finden zu können. So erging es nicht nur mir, sondern vielen.

Danach folgte mein erster vServer. Damals von der Keyweb AG. Da ich keinen Schimmer von Linux hatte, wurde es ein Windows vServer. Die Kiste hatte aber definitiv zu wenig RAM bzw. ich zu wenig Budget für mehr RAM und zu wenig Erfahrung mit Windows Server. Ende vom Lied? Downtimes ohne Ende. Von vServern hatte ich vorerst genug und wechselte wieder zurück zu einem schlichten Webspace.

Diesen mietete ich bei Hetzner, über die ich nur Gutes berichten kann. Das Paket “Level 4″, welches ich damals buchte, gibt es bis heute noch und für kleinere Blogs mag das sicherlich reichen. Da ich jedoch mittlerweile den einen oder anderen Blog betrieb und einige davon sehr gut besucht waren (und immer noch sind) musste etwas Größeres her. So kam ich wieder zu einem vServer. Diesmal jedoch mit Linux. Nach einiger Recherche entschied ich mich für Netcup.de als Hoster, die auch im aktuellen vServer-Vergleich ein sehr gutes Ergebnis hinlegen. (Gemessen an RAM, Speicher und Preis.)

Warum Netcup als Hoster?

Wie im letzten Absatz schon angedeutet, ist Netcup vom Preis-/Leistungsverhältnis her ziemlich gut. Soviel (v-)Server für so wenig Geld habe ich bisher noch nicht finden können. Zwar bin ich noch in einem alten Tarif, den es mittlerweile nicht mehr gibt, jedoch sind auch die neuen/aktuellen Angebote in meinen Augen noch mehr als gut.

Das Nachfolgeangebot zu meinem Tarif kostet monatlich 8,49 € und beinhaltet folgende “Hardfacts”:

  • Intel XEON E5-2620 (1 CPU Kern zugesichert)
  • 4GB DDR3 RAM
  • 240 GB Festplatte

Für das Geld finde ich die gebotene Leistung anständig. Der eigentliche Grund, warum ich mich schlussendlich für Netcup entschieden hatte, war aber das Backend, sprich die Weboberfläche, über die ich den Server verwalten kann.

Von anderen Anbietern kannte (selber nie besessen, aber bei Freunden gesehen) ich es, dass man bei vServern verschiedene Betriebssysteme aufspielen konnte. Zur Wahl standen immer ein paar Images von Betriebssystemen, die der jeweilige Hoster hinterlegt hat. Man war/ist also darauf festgelegt, was der Hoster anbietet.

Bei Netcup habe ich aber die Möglichkeit jedes beliebige CD-/DVD-Image hochzuladen. Über das Backend kann ich dann das jeweilige Image in ein “CD-Laufwerk” einlegen und installieren. Per Remote-Konsole lässt sich arbeiten, als säße ich direkt vor dem Server. Und genau das waren die Punkte, die mich dann letztendlich dazu bewegt haben zu Netcup zu wechseln, da diese Features bei den anderen Anbietern entweder gar nicht oder nur bei wesentlich teureren Produkten vorhanden waren.

Netcup Gutscheine für euch

Wenn ich schon einmal über meinen Hoster berichte, dann sollt ihr natürlich auch nicht leer ausgehen. Im Backend habe ich einen Link für ein “Kunden werben Kunden”-Programm gefunden, in dem ich auch Gutscheine generieren kann.

Sollte also jemand auf den Geschmack gekommen sein, schreibt mir kurz eine Mail oder einen Kommentar unter diesen Artikel, dann kann ich euch einen passenden Gutschein generieren. Derzeit kann ich euch folgenden Gutschein anbieten:

  • 5€ Neukunden-Gutschein

Die Vertragslaufzeit beträgt 1 Monat, sodass man monatlich kündigen kann. Gezahlt wird jeweils für 3 Monate im voraus. Zuviel Gezahltes dürfte wohl zurück erstattet werden. (Ganz genau kann ich es nicht sagen, da ich noch nicht gekündigt habe. Bei Interesse kann ich aber gerne einmal nachfragen.)

Jetzt seid ihr dran

Soviel zu meinem Hoster und warum ich mich für eben diesen entschieden habe. Nun würde mich interessieren, bei welchem Hoster ihr seid und welche Produkte (in etwa) ihr nutzt. Sprich habt ihr einen kleinen Webspace oder gleich den “ganz dicken” Server?

 

Das Artikelbild steht unter CC-Lizenz und stamm von Rudolf Schuba.

1

HP Stream, die Alternative zu Chromebooks

HP StreamVor Kurzem hat HP mit seinem Modell HP Stream ein ziemlich günstiges Notebook vorgestellt. Wobei ziemlich günstig noch untertrieben ist. Preislich bewegt sich das HP Stream nämlich noch unter der Netbook-Konkurrenz und sogar noch knapp unter den günstigsten Chromebooks.

Rund 200$ möchte HP für den HP Stream mit Windows 8.1 haben. Umgerechnet und mit Mehrwertsteuer versehen, werden in Deutschland dann wohl rund 200€ daraus werden. Das sind immer noch gut 50€ weniger als das derzeit günstigste Chromebook kostet. Doch was bietet HP für so wenig Geld?

HP Stream – Technische Daten

Das Stream wird mit einem 14-Zoll-Display gefertigt, welches eine Auflösung von 1366×768 Pixeln hat. Das ist jetzt nicht weltbewegend, aber durchaus noch brauchbar und für den Preis ok.

Als Prozessor kommt ein AMD A4 Micro-6400T Quadcore zum Einsatz. Dieser ist pro Core standardmäßig mit 1 GHz getaktet, die bei Bedarf aber auf bis zu 1.6 GHz dynamisch erhöht werden. Laut Mobilegeeks kommt der HP Stream dafür ohne aktive Lüfter aus. Das heißt, dass das Gerät extrem leise sein wird.

Der RAM ist mit 2 GB eher knapp bemessen und leider fest verlötet. Statt einer Festplatte kommt, je nach Ausstattungsvariante ein 32 bzw. 64 GB großer Flashspeicher (SSD) zum Einsatz.

Eine dedizierte Grafikkarte gibt es nicht. Dies übernimmt die im AMD-Prozessor integrierte Radion R3 Grafikeinheit.

Ansonsten bietet der HP Stream mit einer 720p Webcam, USB 3.0 und USB 2.0 Anschlüssen, SD-Karten-Leser, Bluetooth 4.0, n-WLAN und Beats-Audio relativ viel fürs Geld.

HP Stream vs. Chromebook

Doch nun zurück zur Eingangsfrage. Der HP Stream ist günstiger als ein Chromebook und somit so ziemlich das Günstigste, was man demnächst auf dem Markt bekommen dürfte. Doch ist er eine Alternative bzw. Konkurrenz zu den Chromebooks?

Von den Leistungsdaten her ist das HP Stream sicherlich kein Überflieger, aber das sind die Chromebooks auch nicht. Ein großer, wenn nicht der größte Vorteil, liegt in meinen Augen, jedoch in der Wahl der Betriebssystems. Mit Windows 8.1 bekommt der Nutzer ein Betriebssystem, mit dem sich auch offline Arbeiten lässt. Zudem hat man die Wahl zwischen mehreren Millionen an Programmen, die auf der Windows Plattform lauffähig sind.

Ein Chromebook hingegen arbeitet ausschließlich im Browser und fühlt sich am wohlsten, wenn es eine Verbindung zum Internet hat. Zwar sind einige der Chrome-Applikationen mittlerweile auch offline-fähig, jedoch beschränkt sich die Produktivität eines Chromebooks bei fehlendem Netz auf ein Minimum.

Fazit

Bei der Frage Chromebook oder Low-Budget Windows 8.1 Gerät, muss der Nutzer seinen eigenen Anforderungen hinterfragen. Handelt es sich um einen reinen Webworker, dessen Arbeit sich sowieso ausschließlich im Internet abhandelt und der somit dauerhaft online ist, dann ist ein Chromebook sicherlich keine schlechte Wahl.

Wer etwas mehr Flexibilität bei der Wahl der Anwendungen möchte und auch gerne öfters, wenn nicht sogar primär, offline arbeitet, der sollte vielleicht eher zu einem Low-End Windows 8.1 Notebook greifen, wie es das HP Stream ist.

Seite 1 von 51